Skip to content

Commit ce140a3

Browse files
authored
fix: ledger compatibility with v1 (#377)
1 parent 6be26e2 commit ce140a3

File tree

4 files changed

+212
-23
lines changed

4 files changed

+212
-23
lines changed

components/fctl/cmd/ledger/internal/transaction.go

Lines changed: 101 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,18 @@ package internal
22

33
import (
44
"context"
5-
"encoding/json"
65
"errors"
76
"fmt"
87
"net/http"
8+
"reflect"
99
"strconv"
1010
"strings"
11+
"unsafe"
1112

1213
"github.com/formancehq/formance-sdk-go"
1314
"github.com/formancehq/formance-sdk-go/pkg/models/operations"
1415
"github.com/formancehq/formance-sdk-go/pkg/models/shared"
15-
"github.com/formancehq/stack/libs/go-libs/api"
16+
"github.com/formancehq/formance-sdk-go/pkg/utils"
1617
"github.com/formancehq/stack/libs/go-libs/collectionutils"
1718
"golang.org/x/mod/semver"
1819
)
@@ -55,7 +56,97 @@ func TransactionIDOrLastN(ctx context.Context, ledgerClient *formance.Formance,
5556
return strconv.ParseInt(id, 10, 64)
5657
}
5758

58-
func CreateTransaction(client *formance.Formance, ctx context.Context, ledger string, request operations.CreateTransactionRequest) (*shared.Transaction, error) {
59+
// CreateTransactionResponse - OK
60+
type CreateTransactionResponse struct {
61+
Data []shared.Transaction `json:"data"`
62+
}
63+
64+
type CreateTransactionWrapper struct {
65+
ContentType string
66+
// OK
67+
CreateTransactionResponse *CreateTransactionResponse
68+
// Error
69+
ErrorResponse *shared.ErrorResponse
70+
StatusCode int
71+
RawResponse *http.Response
72+
}
73+
74+
// CreateTransaction - Create a new transaction to a ledger
75+
func createTransactionV1(ctx context.Context, client *formance.Formance, baseURL string, request operations.CreateTransactionRequest) (*CreateTransactionWrapper, error) {
76+
77+
// Dirty hack to get the http client from the sdk client struct
78+
field := reflect.ValueOf(client).Elem().FieldByName("_securityClient")
79+
httpClient := reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).Elem().Interface().(formance.HTTPClient)
80+
81+
url, err := utils.GenerateURL(ctx, baseURL, "/api/ledger/{ledger}/transactions", request, nil)
82+
if err != nil {
83+
return nil, fmt.Errorf("error generating URL: %w", err)
84+
}
85+
86+
bodyReader, reqContentType, err := utils.SerializeRequestBody(ctx, request, "PostTransaction", "json")
87+
if err != nil {
88+
return nil, fmt.Errorf("error serializing request body: %w", err)
89+
}
90+
if bodyReader == nil {
91+
return nil, fmt.Errorf("request body is required")
92+
}
93+
94+
req, err := http.NewRequestWithContext(ctx, "POST", url, bodyReader)
95+
if err != nil {
96+
return nil, fmt.Errorf("error creating request: %w", err)
97+
}
98+
req.Header.Set("Accept", "application/json;q=1, application/json;q=0")
99+
req.Header.Set("Content-Type", reqContentType)
100+
101+
utils.PopulateHeaders(ctx, req, request)
102+
103+
if err := utils.PopulateQueryParams(ctx, req, request, nil); err != nil {
104+
return nil, fmt.Errorf("error populating query params: %w", err)
105+
}
106+
107+
httpRes, err := httpClient.Do(req)
108+
if err != nil {
109+
return nil, fmt.Errorf("error sending request: %w", err)
110+
}
111+
if httpRes == nil {
112+
return nil, fmt.Errorf("error sending request: no response")
113+
}
114+
defer httpRes.Body.Close()
115+
116+
contentType := httpRes.Header.Get("Content-Type")
117+
118+
res := &CreateTransactionWrapper{
119+
StatusCode: httpRes.StatusCode,
120+
ContentType: contentType,
121+
RawResponse: httpRes,
122+
}
123+
switch {
124+
case httpRes.StatusCode == 200:
125+
switch {
126+
case utils.MatchContentType(contentType, `application/json`):
127+
var out *CreateTransactionResponse
128+
if err := utils.UnmarshalJsonFromResponseBody(httpRes.Body, &out); err != nil {
129+
return nil, err
130+
}
131+
132+
res.CreateTransactionResponse = out
133+
}
134+
default:
135+
switch {
136+
case utils.MatchContentType(contentType, `application/json`):
137+
var out *shared.ErrorResponse
138+
if err := utils.UnmarshalJsonFromResponseBody(httpRes.Body, &out); err != nil {
139+
return nil, err
140+
}
141+
142+
res.ErrorResponse = out
143+
}
144+
}
145+
146+
return res, nil
147+
}
148+
149+
func CreateTransaction(client *formance.Formance, ctx context.Context, request operations.CreateTransactionRequest) (*shared.Transaction, error) {
59150

60151
versionsResponse, err := client.GetVersions(ctx)
61152
if err != nil {
@@ -69,15 +160,17 @@ func CreateTransaction(client *formance.Formance, ctx context.Context, ledger st
69160
return version.Name == "ledger"
70161
})[0].Version
71162

72-
response, err := client.Ledger.CreateTransaction(ctx, request)
73-
74163
if semver.IsValid(version) && semver.Compare(version, "v2.0.0") < 0 {
75-
baseResponse := api.BaseResponse[[]shared.Transaction]{}
76-
if err := json.NewDecoder(response.RawResponse.Body).Decode(&baseResponse); err != nil {
164+
baseURL := strings.TrimSuffix(versionsResponse.RawResponse.Request.URL.String(), "/versions")
165+
166+
v, err := createTransactionV1(ctx, client, baseURL, request)
167+
if err != nil {
77168
return nil, err
78169
}
79-
return &(*baseResponse.Data)[0], nil
170+
171+
return &v.CreateTransactionResponse.Data[0], nil
80172
} else {
173+
response, err := client.Ledger.CreateTransaction(ctx, request)
81174
if err != nil {
82175
return nil, err
83176
}

components/fctl/cmd/ledger/send.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ func NewSendCommand() *cobra.Command {
7171
}
7272

7373
reference := fctl.GetString(cmd, referenceFlag)
74-
tx, err := internal.CreateTransaction(ledgerClient, cmd.Context(), fctl.GetString(cmd, internal.LedgerFlag), operations.CreateTransactionRequest{
74+
tx, err := internal.CreateTransaction(ledgerClient, cmd.Context(), operations.CreateTransactionRequest{
7575
PostTransaction: shared.PostTransaction{
7676
Metadata: metadata,
7777
Postings: []shared.Posting{

components/fctl/cmd/ledger/transactions/num.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ func NewCommand() *cobra.Command {
120120

121121
ledger := fctl.GetString(cmd, internal.LedgerFlag)
122122

123-
tx, err := internal.CreateTransaction(client, cmd.Context(), ledger, operations.CreateTransactionRequest{
123+
tx, err := internal.CreateTransaction(client, cmd.Context(), operations.CreateTransactionRequest{
124124
PostTransaction: shared.PostTransaction{
125125
Metadata: metadata,
126126
Reference: &reference,

components/orchestration/internal/workflow/activities/activity_ledger_create_transaction.go

Lines changed: 109 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,112 @@ package activities
22

33
import (
44
"context"
5-
"encoding/json"
65
"fmt"
76
"net/http"
7+
"reflect"
8+
"strings"
9+
"unsafe"
810

11+
"github.com/formancehq/formance-sdk-go"
912
"github.com/formancehq/formance-sdk-go/pkg/models/operations"
1013
"github.com/formancehq/formance-sdk-go/pkg/models/shared"
11-
"github.com/formancehq/stack/libs/go-libs/api"
14+
"github.com/formancehq/formance-sdk-go/pkg/utils"
1215
"github.com/formancehq/stack/libs/go-libs/collectionutils"
1316
"go.temporal.io/sdk/temporal"
1417
"go.temporal.io/sdk/workflow"
1518
"golang.org/x/mod/semver"
1619
)
1720

21+
// CreateTransactionResponse - OK
22+
type CreateTransactionResponse struct {
23+
Data []shared.Transaction `json:"data"`
24+
}
25+
26+
type CreateTransactionWrapper struct {
27+
ContentType string
28+
// OK
29+
CreateTransactionResponse *CreateTransactionResponse
30+
// Error
31+
ErrorResponse *shared.ErrorResponse
32+
StatusCode int
33+
RawResponse *http.Response
34+
}
35+
36+
// CreateTransaction - Create a new transaction to a ledger
37+
func createTransactionV1(ctx context.Context, client *formance.Formance, baseURL string, request CreateTransactionRequest) (*CreateTransactionWrapper, error) {
38+
39+
// Dirty hack to get the http client from the sdk client struct
40+
field := reflect.ValueOf(client).Elem().FieldByName("_securityClient")
41+
httpClient := reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).Elem().Interface().(formance.HTTPClient)
42+
43+
url, err := utils.GenerateURL(ctx, baseURL, "/api/ledger/{ledger}/transactions", request, nil)
44+
if err != nil {
45+
return nil, fmt.Errorf("error generating URL: %w", err)
46+
}
47+
48+
bodyReader, reqContentType, err := utils.SerializeRequestBody(ctx, request, "PostTransaction", "json")
49+
if err != nil {
50+
return nil, fmt.Errorf("error serializing request body: %w", err)
51+
}
52+
if bodyReader == nil {
53+
return nil, fmt.Errorf("request body is required")
54+
}
55+
56+
req, err := http.NewRequestWithContext(ctx, "POST", url, bodyReader)
57+
if err != nil {
58+
return nil, fmt.Errorf("error creating request: %w", err)
59+
}
60+
req.Header.Set("Accept", "application/json;q=1, application/json;q=0")
61+
req.Header.Set("Content-Type", reqContentType)
62+
63+
utils.PopulateHeaders(ctx, req, request)
64+
65+
if err := utils.PopulateQueryParams(ctx, req, request, nil); err != nil {
66+
return nil, fmt.Errorf("error populating query params: %w", err)
67+
}
68+
69+
httpRes, err := httpClient.Do(req)
70+
if err != nil {
71+
return nil, fmt.Errorf("error sending request: %w", err)
72+
}
73+
if httpRes == nil {
74+
return nil, fmt.Errorf("error sending request: no response")
75+
}
76+
defer httpRes.Body.Close()
77+
78+
contentType := httpRes.Header.Get("Content-Type")
79+
80+
res := &CreateTransactionWrapper{
81+
StatusCode: httpRes.StatusCode,
82+
ContentType: contentType,
83+
RawResponse: httpRes,
84+
}
85+
switch {
86+
case httpRes.StatusCode == 200:
87+
switch {
88+
case utils.MatchContentType(contentType, `application/json`):
89+
var out *CreateTransactionResponse
90+
if err := utils.UnmarshalJsonFromResponseBody(httpRes.Body, &out); err != nil {
91+
return nil, err
92+
}
93+
94+
res.CreateTransactionResponse = out
95+
}
96+
default:
97+
switch {
98+
case utils.MatchContentType(contentType, `application/json`):
99+
var out *shared.ErrorResponse
100+
if err := utils.UnmarshalJsonFromResponseBody(httpRes.Body, &out); err != nil {
101+
return nil, err
102+
}
103+
104+
res.ErrorResponse = out
105+
}
106+
}
107+
108+
return res, nil
109+
}
110+
18111
type CreateTransactionRequest struct {
19112
Ledger string `json:"ledger"`
20113
Data shared.PostTransaction `json:"data"`
@@ -33,23 +126,26 @@ func (a Activities) CreateTransaction(ctx context.Context, request CreateTransac
33126
return version.Name == "ledger"
34127
})[0].Version
35128

36-
response, err := a.client.Ledger.
37-
CreateTransaction(
38-
ctx,
39-
operations.CreateTransactionRequest{
40-
PostTransaction: request.Data,
41-
Ledger: request.Ledger,
42-
},
43-
)
44129
if semver.IsValid(version) && semver.Compare(version, "v2.0.0") < 0 {
45-
baseResponse := api.BaseResponse[[]shared.Transaction]{}
46-
if err := json.NewDecoder(response.RawResponse.Body).Decode(&baseResponse); err != nil {
130+
baseURL := strings.TrimSuffix(versionsResponse.RawResponse.Request.URL.String(), "/versions")
131+
132+
v, err := createTransactionV1(ctx, a.client, baseURL, request)
133+
if err != nil {
47134
return nil, err
48135
}
136+
49137
return &shared.CreateTransactionResponse{
50-
Data: (*baseResponse.Data)[0],
138+
Data: v.CreateTransactionResponse.Data[0],
51139
}, nil
52140
} else {
141+
response, err := a.client.Ledger.
142+
CreateTransaction(
143+
ctx,
144+
operations.CreateTransactionRequest{
145+
PostTransaction: request.Data,
146+
Ledger: request.Ledger,
147+
},
148+
)
53149
if err != nil {
54150
return nil, err
55151
}

0 commit comments

Comments
 (0)