Skip to content

Commit

Permalink
feat: make idempotency feature check inputs
Browse files Browse the repository at this point in the history
  • Loading branch information
gfyrag committed Oct 16, 2024
1 parent ebfc7bd commit 4594f3d
Show file tree
Hide file tree
Showing 50 changed files with 759 additions and 449 deletions.
18 changes: 10 additions & 8 deletions internal/numscript.go → internal/api/common/numscript.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package ledger
package common

import (
"fmt"
"github.com/formancehq/ledger/internal"
ledgercontroller "github.com/formancehq/ledger/internal/controller/ledger"
"sort"
"strings"

Expand All @@ -13,15 +15,15 @@ type variable struct {
value string
}

func TxToScriptData(txData TransactionData, allowUnboundedOverdrafts bool) RunScript {
func TxToScriptData(txData ledger.TransactionData, allowUnboundedOverdrafts bool) ledgercontroller.RunScript {
sb := strings.Builder{}
monetaryToVars := map[string]variable{}
accountsToVars := map[string]variable{}
i := 0
j := 0
for _, p := range txData.Postings {
if _, ok := accountsToVars[p.Source]; !ok {
if p.Source != WORLD {
if p.Source != ledger.WORLD {
accountsToVars[p.Source] = variable{
name: fmt.Sprintf("va%d", i),
value: p.Source,
Expand All @@ -30,7 +32,7 @@ func TxToScriptData(txData TransactionData, allowUnboundedOverdrafts bool) RunSc
}
}
if _, ok := accountsToVars[p.Destination]; !ok {
if p.Destination != WORLD {
if p.Destination != ledger.WORLD {
accountsToVars[p.Destination] = variable{
name: fmt.Sprintf("va%d", i),
value: p.Destination,
Expand Down Expand Up @@ -74,7 +76,7 @@ func TxToScriptData(txData TransactionData, allowUnboundedOverdrafts bool) RunSc
panic(fmt.Sprintf("monetary %s not found", m))
}
sb.WriteString(fmt.Sprintf("send $%s (\n", mon.name))
if p.Source == WORLD {
if p.Source == ledger.WORLD {
sb.WriteString("\tsource = @world\n")
} else {
src, ok := accountsToVars[p.Source]
Expand All @@ -87,7 +89,7 @@ func TxToScriptData(txData TransactionData, allowUnboundedOverdrafts bool) RunSc
}
sb.WriteString("\n")
}
if p.Destination == WORLD {
if p.Destination == ledger.WORLD {
sb.WriteString("\tdestination = @world\n")
} else {
dest, ok := accountsToVars[p.Destination]
Expand All @@ -111,8 +113,8 @@ func TxToScriptData(txData TransactionData, allowUnboundedOverdrafts bool) RunSc
txData.Metadata = metadata.Metadata{}
}

return RunScript{
Script: Script{
return ledgercontroller.RunScript{
Script: ledgercontroller.Script{
Plain: sb.String(),
Vars: vars,
},
Expand Down
10 changes: 7 additions & 3 deletions internal/api/v1/controllers_accounts_add_metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package v1

import (
"encoding/json"
"github.com/formancehq/ledger/internal/controller/ledger"
"github.com/formancehq/ledger/pkg/accounts"
"net/http"
"net/url"
Expand All @@ -15,13 +16,13 @@ import (

func addAccountMetadata(w http.ResponseWriter, r *http.Request) {
l := common.LedgerFromContext(r.Context())
param, err := url.PathUnescape(chi.URLParam(r, "address"))
address, err := url.PathUnescape(chi.URLParam(r, "address"))
if err != nil {
api.BadRequestWithDetails(w, ErrValidation, err, err.Error())
return
}

if !accounts.ValidateAddress(param) {
if !accounts.ValidateAddress(address) {
api.BadRequest(w, ErrValidation, errors.New("invalid account address format"))
return
}
Expand All @@ -32,7 +33,10 @@ func addAccountMetadata(w http.ResponseWriter, r *http.Request) {
return
}

err = l.SaveAccountMetadata(r.Context(), getCommandParameters(r), param, m)
err = l.SaveAccountMetadata(r.Context(), getCommandParameters(r, ledger.SaveAccountMetadata{
Address: address,
Metadata: m,
}))
if err != nil {
api.InternalServerError(w, r, err)
return
Expand Down
7 changes: 6 additions & 1 deletion internal/api/v1/controllers_accounts_add_metadata_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,12 @@ func TestAccountsAddMetadata(t *testing.T) {
systemController, ledgerController := newTestingSystemController(t, true)
if testCase.expectStatusCode == http.StatusNoContent {
ledgerController.EXPECT().
SaveAccountMetadata(gomock.Any(), ledgercontroller.Parameters{}, testCase.account, testCase.body).
SaveAccountMetadata(gomock.Any(), ledgercontroller.Parameters[ledgercontroller.SaveAccountMetadata]{
Input: ledgercontroller.SaveAccountMetadata{
Address: testCase.account,
Metadata: testCase.body.(metadata.Metadata),
},
}).
Return(nil)
}

Expand Down
10 changes: 6 additions & 4 deletions internal/api/v1/controllers_accounts_delete_metadata.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package v1

import (
"github.com/formancehq/ledger/internal/controller/ledger"
"net/http"
"net/url"

Expand All @@ -10,7 +11,7 @@ import (
)

func deleteAccountMetadata(w http.ResponseWriter, r *http.Request) {
param, err := url.PathUnescape(chi.URLParam(r, "address"))
address, err := url.PathUnescape(chi.URLParam(r, "address"))
if err != nil {
api.BadRequestWithDetails(w, ErrValidation, err, err.Error())
return
Expand All @@ -19,9 +20,10 @@ func deleteAccountMetadata(w http.ResponseWriter, r *http.Request) {
if err := common.LedgerFromContext(r.Context()).
DeleteAccountMetadata(
r.Context(),
getCommandParameters(r),
param,
chi.URLParam(r, "key"),
getCommandParameters(r, ledger.DeleteAccountMetadata{
Address: address,
Key: chi.URLParam(r, "key"),
}),
); err != nil {
api.InternalServerError(w, r, err)
return
Expand Down
10 changes: 9 additions & 1 deletion internal/api/v1/controllers_accounts_delete_metadata_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,15 @@ func TestAccountsDeleteMetadata(t *testing.T) {

if tc.expectBackendCall {
ledgerController.EXPECT().
DeleteAccountMetadata(gomock.Any(), ledgercontroller.Parameters{}, tc.account, "foo").
DeleteAccountMetadata(
gomock.Any(),
ledgercontroller.Parameters[ledgercontroller.DeleteAccountMetadata]{
Input: ledgercontroller.DeleteAccountMetadata{
Address: tc.account,
Key: "foo",
},
},
).
Return(tc.returnErr)
}

Expand Down
5 changes: 4 additions & 1 deletion internal/api/v1/controllers_transactions_add_metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ func addTransactionMetadata(w http.ResponseWriter, r *http.Request) {
return
}

if err := l.SaveTransactionMetadata(r.Context(), getCommandParameters(r), int(txID), m); err != nil {
if err := l.SaveTransactionMetadata(r.Context(), getCommandParameters(r, ledgercontroller.SaveTransactionMetadata{
TransactionID: int(txID),
Metadata: m,
})); err != nil {
switch {
case errors.Is(err, ledgercontroller.ErrNotFound):
api.NotFound(w, err)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,12 @@ func TestTransactionsAddMetadata(t *testing.T) {
systemController, ledgerController := newTestingSystemController(t, true)
if testCase.expectStatusCode == http.StatusNoContent {
ledgerController.EXPECT().
SaveTransactionMetadata(gomock.Any(), ledgercontroller.Parameters{}, 0, testCase.body).
SaveTransactionMetadata(gomock.Any(), ledgercontroller.Parameters[ledgercontroller.SaveTransactionMetadata]{
Input: ledgercontroller.SaveTransactionMetadata{
TransactionID: 0,
Metadata: testCase.body.(metadata.Metadata),
},
}).
Return(nil)
}

Expand Down
18 changes: 10 additions & 8 deletions internal/api/v1/controllers_transactions_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ import (
)

type Script struct {
ledger.Script
ledgercontroller.Script
Vars map[string]json.RawMessage `json:"vars"`
}

func (s Script) ToCore() (*ledger.Script, error) {
func (s Script) ToCore() (*ledgercontroller.Script, error) {
s.Script.Vars = map[string]string{}
for k, v := range s.Vars {

Expand Down Expand Up @@ -83,7 +83,7 @@ func createTransaction(w http.ResponseWriter, r *http.Request) {
Metadata: payload.Metadata,
}

res, err := l.CreateTransaction(r.Context(), getCommandParameters(r), ledger.TxToScriptData(txData, false))
res, err := l.CreateTransaction(r.Context(), getCommandParameters(r, common.TxToScriptData(txData, false)))
if err != nil {
switch {
case errors.Is(err, &ledgercontroller.ErrInsufficientFunds{}):
Expand All @@ -92,9 +92,10 @@ func createTransaction(w http.ResponseWriter, r *http.Request) {
api.BadRequest(w, ErrScriptCompilationFailed, err)
case errors.Is(err, &ledgercontroller.ErrMetadataOverride{}):
api.BadRequest(w, ErrScriptMetadataOverride, err)
case errors.Is(err, ledgercontroller.ErrNoPostings):
case errors.Is(err, ledgercontroller.ErrNoPostings) ||
errors.Is(err, ledgercontroller.ErrInvalidIdempotencyInput{}):
api.BadRequest(w, ErrValidation, err)
case errors.Is(err, ledgercontroller.ErrReferenceConflict{}):
case errors.Is(err, ledgercontroller.ErrTransactionReferenceConflict{}):
api.WriteErrorResponse(w, http.StatusConflict, ErrConflict, err)
default:
api.InternalServerError(w, r, err)
Expand All @@ -111,24 +112,25 @@ func createTransaction(w http.ResponseWriter, r *http.Request) {
return
}

runScript := ledger.RunScript{
runScript := ledgercontroller.RunScript{
Script: *script,
Timestamp: payload.Timestamp,
Reference: payload.Reference,
Metadata: payload.Metadata,
}

res, err := l.CreateTransaction(r.Context(), getCommandParameters(r), runScript)
res, err := l.CreateTransaction(r.Context(), getCommandParameters(r, runScript))
if err != nil {
switch {
case errors.Is(err, &ledgercontroller.ErrInsufficientFunds{}):
api.BadRequest(w, ErrInsufficientFund, err)
case errors.Is(err, &ledgercontroller.ErrInvalidVars{}) ||
errors.Is(err, ledgercontroller.ErrCompilationFailed{}) ||
errors.Is(err, &ledgercontroller.ErrMetadataOverride{}) ||
errors.Is(err, ledgercontroller.ErrInvalidIdempotencyInput{}) ||
errors.Is(err, ledgercontroller.ErrNoPostings):
api.BadRequest(w, ErrValidation, err)
case errors.Is(err, ledgercontroller.ErrReferenceConflict{}):
case errors.Is(err, ledgercontroller.ErrTransactionReferenceConflict{}):
api.WriteErrorResponse(w, http.StatusConflict, ErrConflict, err)
default:
api.InternalServerError(w, r, err)
Expand Down
38 changes: 20 additions & 18 deletions internal/api/v1/controllers_transactions_create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package v1

import (
"encoding/json"
"github.com/formancehq/ledger/internal/api/common"
"math/big"
"net/http"
"net/http/httptest"
Expand All @@ -23,7 +24,7 @@ func TestTransactionsCreate(t *testing.T) {
type testCase struct {
name string
expectedPreview bool
expectedRunScript ledger.RunScript
expectedRunScript ledgercontroller.RunScript
payload any
expectedStatusCode int
expectedErrorCode string
Expand All @@ -35,13 +36,13 @@ func TestTransactionsCreate(t *testing.T) {
name: "using plain numscript",
payload: CreateTransactionRequest{
Script: Script{
Script: ledger.Script{
Script: ledgercontroller.Script{
Plain: `XXX`,
},
},
},
expectedRunScript: ledger.RunScript{
Script: ledger.Script{
expectedRunScript: ledgercontroller.RunScript{
Script: ledgercontroller.Script{
Plain: `XXX`,
Vars: map[string]string{},
},
Expand All @@ -51,7 +52,7 @@ func TestTransactionsCreate(t *testing.T) {
name: "using plain numscript with variables",
payload: CreateTransactionRequest{
Script: Script{
Script: ledger.Script{
Script: ledgercontroller.Script{
Plain: `vars {
monetary $val
}
Expand All @@ -66,8 +67,8 @@ func TestTransactionsCreate(t *testing.T) {
},
},
},
expectedRunScript: ledger.RunScript{
Script: ledger.Script{
expectedRunScript: ledgercontroller.RunScript{
Script: ledgercontroller.Script{
Plain: `vars {
monetary $val
}
Expand All @@ -86,7 +87,7 @@ func TestTransactionsCreate(t *testing.T) {
name: "using plain numscript with variables (legacy format)",
payload: CreateTransactionRequest{
Script: Script{
Script: ledger.Script{
Script: ledgercontroller.Script{
Plain: `vars {
monetary $val
}
Expand All @@ -104,8 +105,8 @@ func TestTransactionsCreate(t *testing.T) {
},
},
},
expectedRunScript: ledger.RunScript{
Script: ledger.Script{
expectedRunScript: ledgercontroller.RunScript{
Script: ledgercontroller.Script{
Plain: `vars {
monetary $val
}
Expand All @@ -124,16 +125,16 @@ func TestTransactionsCreate(t *testing.T) {
name: "using plain numscript and dry run",
payload: CreateTransactionRequest{
Script: Script{
Script: ledger.Script{
Script: ledgercontroller.Script{
Plain: `send (
source = @world
destination = @bank
)`,
},
},
},
expectedRunScript: ledger.RunScript{
Script: ledger.Script{
expectedRunScript: ledgercontroller.RunScript{
Script: ledgercontroller.Script{
Plain: `send (
source = @world
destination = @bank
Expand All @@ -153,7 +154,7 @@ func TestTransactionsCreate(t *testing.T) {
ledger.NewPosting("world", "bank", "USD", big.NewInt(100)),
},
},
expectedRunScript: ledger.TxToScriptData(ledger.NewTransactionData().WithPostings(
expectedRunScript: common.TxToScriptData(ledger.NewTransactionData().WithPostings(
ledger.NewPosting("world", "bank", "USD", big.NewInt(100)),
), false),
},
Expand All @@ -168,7 +169,7 @@ func TestTransactionsCreate(t *testing.T) {
},
},
expectedPreview: true,
expectedRunScript: ledger.TxToScriptData(ledger.NewTransactionData().WithPostings(
expectedRunScript: common.TxToScriptData(ledger.NewTransactionData().WithPostings(
ledger.NewPosting("world", "bank", "USD", big.NewInt(100)),
), false),
},
Expand All @@ -190,7 +191,7 @@ func TestTransactionsCreate(t *testing.T) {
},
},
Script: Script{
Script: ledger.Script{
Script: ledgercontroller.Script{
Plain: `
send [COIN 100] (
source = @world
Expand Down Expand Up @@ -225,9 +226,10 @@ func TestTransactionsCreate(t *testing.T) {
if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 {
testCase.expectedRunScript.Timestamp = time.Time{}
ledgerController.EXPECT().
CreateTransaction(gomock.Any(), ledgercontroller.Parameters{
CreateTransaction(gomock.Any(), ledgercontroller.Parameters[ledgercontroller.RunScript]{
DryRun: tc.expectedPreview,
}, testCase.expectedRunScript).
Input: testCase.expectedRunScript,
}).
Return(pointer.For(expectedTx), nil)
}

Expand Down
Loading

0 comments on commit 4594f3d

Please sign in to comment.