diff --git a/cmd/serve.go b/cmd/serve.go index 5c6f30ff7..26cd9cce2 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -1,6 +1,10 @@ package cmd import ( + "net/http" + "net/http/pprof" + "time" + apilib "github.com/formancehq/go-libs/v2/api" "github.com/formancehq/go-libs/v2/health" "github.com/formancehq/go-libs/v2/httpserver" @@ -8,9 +12,8 @@ import ( "github.com/formancehq/ledger/internal/storage/driver" "github.com/go-chi/chi/v5" "go.opentelemetry.io/otel/sdk/metric" - "net/http" - "net/http/pprof" - "time" + + "github.com/formancehq/ledger/internal/bus" "github.com/formancehq/go-libs/v2/auth" "github.com/formancehq/go-libs/v2/aws/iam" @@ -19,7 +22,7 @@ import ( "github.com/formancehq/go-libs/v2/otlp/otlptraces" "github.com/formancehq/go-libs/v2/publish" "github.com/formancehq/ledger/internal/api" - "github.com/formancehq/ledger/internal/bus" + ledgercontroller "github.com/formancehq/ledger/internal/controller/ledger" systemcontroller "github.com/formancehq/ledger/internal/controller/system" "github.com/formancehq/ledger/internal/storage" @@ -37,6 +40,7 @@ const ( AutoUpgradeFlag = "auto-upgrade" ExperimentalFeaturesFlag = "experimental-features" BulkMaxSizeFlag = "bulk-max-size" + NumscriptInterpreterFlag = "experimental-numscript-interpreter" ) func NewServeCommand() *cobra.Command { @@ -55,6 +59,7 @@ func NewServeCommand() *cobra.Command { if err != nil { return err } + numscriptInterpreter, _ := cmd.Flags().GetBool(NumscriptInterpreterFlag) bulkMaxSize, err := cmd.Flags().GetInt(BulkMaxSizeFlag) if err != nil { @@ -71,6 +76,7 @@ func NewServeCommand() *cobra.Command { bunconnect.Module(*connectionOptions, service.IsDebug(cmd)), storage.NewFXModule(serveConfiguration.autoUpgrade), systemcontroller.NewFXModule(systemcontroller.ModuleConfiguration{ + NumscriptInterpreter: numscriptInterpreter, NSCacheConfiguration: ledgercontroller.CacheConfiguration{ MaxCount: serveConfiguration.numscriptCacheMaxCount, }, @@ -120,6 +126,7 @@ func NewServeCommand() *cobra.Command { cmd.Flags().String(BindFlag, "0.0.0.0:3068", "API bind address") cmd.Flags().Bool(ExperimentalFeaturesFlag, false, "Enable features configurability") cmd.Flags().Int(BulkMaxSizeFlag, api.DefaultBulkMaxSize, "Bulk max size (default 100)") + cmd.Flags().Bool(NumscriptInterpreterFlag, false, "Enable experimental numscript rewrite") service.AddFlags(cmd.Flags()) bunconnect.AddFlags(cmd.Flags()) diff --git a/docs/api/README.md b/docs/api/README.md index 7cd5c29c8..063650048 100644 --- a/docs/api/README.md +++ b/docs/api/README.md @@ -3189,6 +3189,8 @@ Authorization ( Scopes: ledger:write ) |*anonymous*|IMPORT| |*anonymous*|TIMEOUT| |*anonymous*|BULK_SIZE_EXCEEDED| +|*anonymous*|INTERPRETER_PARSE| +|*anonymous*|INTERPRETER_RUNTIME|

V2LedgerInfoResponse

diff --git a/go.mod b/go.mod index fdcc389b5..7b9548711 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/formancehq/ledger -go 1.22.0 +go 1.22.1 toolchain go1.22.7 @@ -14,12 +14,14 @@ require ( github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 github.com/bluele/gcache v0.0.2 github.com/formancehq/go-libs/v2 v2.0.1-0.20241022185745-110c95803b63 + github.com/formancehq/numscript v0.0.9-0.20241009144012-1150c14a1417 github.com/formancehq/stack/ledger/client v0.0.0-00010101000000-000000000000 github.com/go-chi/chi/v5 v5.1.0 github.com/go-chi/cors v1.2.1 github.com/google/go-cmp v0.6.0 github.com/google/uuid v1.6.0 github.com/invopop/jsonschema v0.12.0 + github.com/jackc/pgx/v5 v5.7.1 github.com/jamiealquiza/tachymeter v2.0.0+incompatible github.com/logrusorgru/aurora v2.0.3+incompatible github.com/nats-io/nats.go v1.37.0 @@ -27,6 +29,7 @@ require ( github.com/onsi/gomega v1.34.2 github.com/ory/dockertest/v3 v3.11.0 github.com/pborman/uuid v1.2.1 + github.com/pkg/errors v0.9.1 github.com/shomali11/xsql v0.0.0-20190608141458-bf76292144df github.com/spf13/cobra v1.8.1 github.com/spf13/pflag v1.0.5 @@ -39,7 +42,7 @@ require ( go.opentelemetry.io/otel/metric v1.31.0 go.opentelemetry.io/otel/sdk/metric v1.31.0 go.opentelemetry.io/otel/trace v1.31.0 - go.uber.org/fx v1.22.2 + go.uber.org/fx v1.23.0 go.uber.org/mock v0.4.0 golang.org/x/oauth2 v0.23.0 golang.org/x/sync v0.8.0 @@ -56,6 +59,7 @@ require ( github.com/ThreeDotsLabs/watermill-kafka/v3 v3.0.5 // indirect github.com/ThreeDotsLabs/watermill-nats/v2 v2.1.1 // indirect github.com/ajg/form v1.5.1 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/aws/aws-msk-iam-sasl-signer-go v1.0.0 // indirect github.com/aws/aws-sdk-go-v2 v1.32.2 // indirect github.com/aws/aws-sdk-go-v2/config v1.27.43 // indirect @@ -111,7 +115,6 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect - github.com/jackc/pgx/v5 v5.7.1 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jcmturner/aescts/v2 v2.0.0 // indirect github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect @@ -137,7 +140,6 @@ require ( github.com/opencontainers/image-spec v1.1.0 // indirect github.com/opencontainers/runc v1.1.14 // indirect github.com/pierrec/lz4/v4 v4.1.21 // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect @@ -182,6 +184,7 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect golang.org/x/crypto v0.28.0 // indirect + golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect golang.org/x/net v0.30.0 // indirect golang.org/x/sys v0.26.0 // indirect golang.org/x/text v0.19.0 // indirect diff --git a/go.sum b/go.sum index b0f8ee3d5..cbd2831e8 100644 --- a/go.sum +++ b/go.sum @@ -24,6 +24,8 @@ github.com/alitto/pond v1.9.2 h1:9Qb75z/scEZVCoSU+osVmQ0I0JOeLfdTDafrbcJ8CLs= github.com/alitto/pond v1.9.2/go.mod h1:xQn3P/sHTYcU/1BR3i86IGIrilcrGC2LiS+E2+CJWsI= github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 h1:yL7+Jz0jTC6yykIK/Wh74gnTJnrGr5AyrNMXuA0gves= github.com/antlr/antlr4/runtime/Go/antlr v1.4.10/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= github.com/aws/aws-msk-iam-sasl-signer-go v1.0.0 h1:UyjtGmO0Uwl/K+zpzPwLoXzMhcN9xmnR2nrqJoBrg3c= github.com/aws/aws-msk-iam-sasl-signer-go v1.0.0/go.mod h1:TJAXuFs2HcMib3sN5L0gUC+Q01Qvy3DemvA55WuC+iA= github.com/aws/aws-sdk-go-v2 v1.32.2 h1:AkNLZEyYMLnx/Q/mSKkcMqwNFXMAvFto9bNsHqcTduI= @@ -95,14 +97,18 @@ github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/formancehq/go-libs/v2 v2.0.1-0.20241017152835-2c30f563ab46 h1:8wZtnWSIYNV7DwD0Jr4HsbcRgezOrgDJ2Q0w9ABieKc= -github.com/formancehq/go-libs/v2 v2.0.1-0.20241017152835-2c30f563ab46/go.mod h1:LgxayMN6wgAQbkB3ioBDTHOVMKp1rC6Q55M1CvG44xY= -github.com/formancehq/go-libs/v2 v2.0.1-0.20241017153232-1a62cecf1a61 h1:GSIhsdo/YXuZXI4q8xA8IrdOkkjfFp6O+DiNywk8s8U= -github.com/formancehq/go-libs/v2 v2.0.1-0.20241017153232-1a62cecf1a61/go.mod h1:LgxayMN6wgAQbkB3ioBDTHOVMKp1rC6Q55M1CvG44xY= github.com/formancehq/go-libs/v2 v2.0.1-0.20241022185745-110c95803b63 h1:DN6gDFwh3zO9VwV6Nt2tj4/BEecyfWfOdHp1YYJ5sBA= github.com/formancehq/go-libs/v2 v2.0.1-0.20241022185745-110c95803b63/go.mod h1:LgxayMN6wgAQbkB3ioBDTHOVMKp1rC6Q55M1CvG44xY= +github.com/formancehq/numscript v0.0.9-0.20241009144012-1150c14a1417 h1:LOd5hxnXDIBcehFrpW1OnXk+VSs0yJXeu1iAOO+Hji4= +github.com/formancehq/numscript v0.0.9-0.20241009144012-1150c14a1417/go.mod h1:btuSv05cYwi9BvLRxVs5zrunU+O1vTgigG1T6UsawcY= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/gkampitakis/ciinfo v0.3.0 h1:gWZlOC2+RYYttL0hBqcoQhM7h1qNkVqvRCV1fOvpAv8= +github.com/gkampitakis/ciinfo v0.3.0/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo= +github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M= +github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk= +github.com/gkampitakis/go-snaps v0.5.4 h1:GX+dkKmVsRenz7SoTbdIEL4KQARZctkMiZ8ZKprRwT8= +github.com/gkampitakis/go-snaps v0.5.4/go.mod h1:ZABkO14uCuVxBHAXAfKG+bqNz+aa1bGPAg8jkI0Nk8Y= github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec= github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= @@ -216,6 +222,8 @@ github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 h1:7UMa6KCCMjZEMD github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo= +github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -299,6 +307,14 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U= +github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU= github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY= github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo= @@ -385,8 +401,8 @@ go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeX go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.uber.org/dig v1.18.0 h1:imUL1UiY0Mg4bqbFfsRQO5G4CGRBec/ZujWTvSVp3pw= go.uber.org/dig v1.18.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE= -go.uber.org/fx v1.22.2 h1:iPW+OPxv0G8w75OemJ1RAnTUrF55zOJlXlo1TbJ0Buw= -go.uber.org/fx v1.22.2/go.mod h1:o/D9n+2mLP6v1EG+qsdT1O8wKopYAsqZasju97SDFCU= +go.uber.org/fx v1.23.0 h1:lIr/gYWQGfTwGcSXWXu4vP5Ws6iqnNEIY+F/aFzCKTg= +go.uber.org/fx v1.23.0/go.mod h1:o/D9n+2mLP6v1EG+qsdT1O8wKopYAsqZasju97SDFCU= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= @@ -402,6 +418,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= diff --git a/internal/api/v2/controllers_bulk.go b/internal/api/v2/controllers_bulk.go index b3b70e8dd..afa1218d9 100644 --- a/internal/api/v2/controllers_bulk.go +++ b/internal/api/v2/controllers_bulk.go @@ -115,6 +115,10 @@ func ProcessBulk( code = ErrNoPostings case errors.Is(err, ledgercontroller.ErrTransactionReferenceConflict{}): code = ErrConflict + case errors.Is(err, ledgercontroller.ErrParsing{}): + code = ErrInterpreterParse + case errors.Is(err, ledgercontroller.ErrRuntime{}): + code = ErrInterpreterRuntime default: code = api.ErrorInternal } diff --git a/internal/api/v2/controllers_transactions_create.go b/internal/api/v2/controllers_transactions_create.go index b54129fbd..912c18b45 100644 --- a/internal/api/v2/controllers_transactions_create.go +++ b/internal/api/v2/controllers_transactions_create.go @@ -7,7 +7,9 @@ import ( ledgercontroller "github.com/formancehq/ledger/internal/controller/ledger" "errors" + "github.com/formancehq/go-libs/v2/api" + "github.com/formancehq/ledger/internal/api/common" ) @@ -50,6 +52,10 @@ func createTransaction(w http.ResponseWriter, r *http.Request) { api.WriteErrorResponse(w, http.StatusConflict, ErrConflict, err) case errors.Is(err, ledgercontroller.ErrInvalidIdempotencyInput{}): api.BadRequest(w, ErrValidation, err) + case errors.Is(err, ledgercontroller.ErrParsing{}): + api.BadRequest(w, ErrInterpreterParse, err) + case errors.Is(err, ledgercontroller.ErrRuntime{}): + api.BadRequest(w, ErrInterpreterRuntime, err) default: common.HandleCommonErrors(w, r, err) } diff --git a/internal/api/v2/errors.go b/internal/api/v2/errors.go index 0e88416c6..7306c6019 100644 --- a/internal/api/v2/errors.go +++ b/internal/api/v2/errors.go @@ -9,4 +9,7 @@ const ( ErrCompilationFailed = "COMPILATION_FAILED" ErrMetadataOverride = "METADATA_OVERRIDE" ErrBulkSizeExceeded = "BULK_SIZE_EXCEEDED" + + ErrInterpreterParse = "INTERPRETER_PARSE" + ErrInterpreterRuntime = "INTERPRETER_RUNTIME" ) diff --git a/internal/controller/ledger/errors.go b/internal/controller/ledger/errors.go index e2572cbb9..00ef329ed 100644 --- a/internal/controller/ledger/errors.go +++ b/internal/controller/ledger/errors.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/formancehq/go-libs/v2/platform/postgres" + "github.com/formancehq/numscript" "github.com/formancehq/ledger/internal/machine" @@ -188,6 +189,35 @@ func newErrCompilationFailed(err error) ErrCompilationFailed { } } +type ErrRuntime struct { + Source string + Inner numscript.InterpreterError +} + +func (e ErrRuntime) Error() string { + return e.Inner.Error() +} + +func (e ErrRuntime) Is(err error) bool { + _, ok := err.(ErrRuntime) + return ok +} + +type ErrParsing struct { + Source string + // Precondition: Errors is not empty + Errors []numscript.ParserError +} + +func (e ErrParsing) Error() string { + return numscript.ParseErrorsToString(e.Errors, e.Source) +} + +func (e ErrParsing) Is(err error) bool { + _, ok := err.(ErrParsing) + return ok +} + // ErrMetadataOverride is used when a metadata is defined at numscript level AND at the input level type ErrMetadataOverride struct { key string diff --git a/internal/controller/ledger/numscript_parser.go b/internal/controller/ledger/numscript_parser.go index a06d2e081..87c373b7d 100644 --- a/internal/controller/ledger/numscript_parser.go +++ b/internal/controller/ledger/numscript_parser.go @@ -6,6 +6,7 @@ import ( "github.com/bluele/gcache" "github.com/formancehq/ledger/internal/machine/script/compiler" + "github.com/formancehq/numscript" ) //go:generate mockgen -write_source_comment=false -write_package_comment=false -source numscript_parser.go -destination numscript_parser_generated_test.go -package ledger . NumscriptParser @@ -32,6 +33,26 @@ func NewDefaultNumscriptParser() *DefaultNumscriptParser { var _ NumscriptParser = (*DefaultNumscriptParser)(nil) +type InterpreterNumscriptParser struct{} + +func (n *InterpreterNumscriptParser) Parse(script string) (NumscriptRuntime, error) { + result := numscript.Parse(script) + errs := result.GetParsingErrors() + if len(errs) != 0 { + return nil, ErrParsing{ + Source: script, + Errors: errs, + } + } + return NewDefaultInterpreterMachineAdapter(result), nil +} + +func NewInterpreterNumscriptParser() *InterpreterNumscriptParser { + return &InterpreterNumscriptParser{} +} + +var _ NumscriptParser = (*InterpreterNumscriptParser)(nil) + type CacheConfiguration struct { MaxCount uint } diff --git a/internal/controller/ledger/numscript_runtime.go b/internal/controller/ledger/numscript_runtime.go index 0effc17fd..eec14e643 100644 --- a/internal/controller/ledger/numscript_runtime.go +++ b/internal/controller/ledger/numscript_runtime.go @@ -4,15 +4,15 @@ import ( "context" "fmt" - "github.com/formancehq/ledger/internal/machine" - "errors" "github.com/formancehq/go-libs/v2/collectionutils" "github.com/formancehq/go-libs/v2/metadata" ledger "github.com/formancehq/ledger/internal" + "github.com/formancehq/ledger/internal/machine" "github.com/formancehq/ledger/internal/machine/vm" "github.com/formancehq/ledger/internal/machine/vm/program" + "github.com/formancehq/numscript" ) type NumscriptExecutionResult struct { @@ -85,3 +85,51 @@ func NewMachineNumscriptRuntimeAdapter(p program.Program) *MachineNumscriptRunti } var _ NumscriptRuntime = (*MachineNumscriptRuntimeAdapter)(nil) + +// numscript rewrite implementation +var _ NumscriptRuntime = (*DefaultInterpreterMachineAdapter)(nil) + +type DefaultInterpreterMachineAdapter struct { + parseResult numscript.ParseResult +} + +func NewDefaultInterpreterMachineAdapter(parseResult numscript.ParseResult) *DefaultInterpreterMachineAdapter { + return &DefaultInterpreterMachineAdapter{ + parseResult: parseResult, + } +} + +func (d *DefaultInterpreterMachineAdapter) Execute(ctx context.Context, tx TX, vars map[string]string) (*NumscriptExecutionResult, error) { + execResult, err := d.parseResult.Run(ctx, vars, newNumscriptRewriteAdapter(tx)) + if err != nil { + return nil, ErrRuntime{ + Source: d.parseResult.GetSource(), + Inner: err, + } + } + + return &NumscriptExecutionResult{ + Postings: collectionutils.Map(execResult.Postings, func(posting numscript.Posting) ledger.Posting { + return ledger.Posting(posting) + }), + Metadata: castMetadata(execResult.Metadata), + AccountMetadata: castAccountsMetadata(execResult.AccountsMetadata), + }, nil +} + +func castMetadata(numscriptMeta numscript.Metadata) metadata.Metadata { + meta := metadata.Metadata{} + for k, v := range numscriptMeta { + meta[k] = v.String() + } + return meta +} + +func castAccountsMetadata(numscriptAccountsMetadata numscript.AccountsMetadata) map[string]metadata.Metadata { + m := make(map[string]metadata.Metadata) + for k, v := range numscriptAccountsMetadata { + m[k] = v + } + return m + +} diff --git a/internal/controller/ledger/store.go b/internal/controller/ledger/store.go index 86a2d6be3..55bc839c4 100644 --- a/internal/controller/ledger/store.go +++ b/internal/controller/ledger/store.go @@ -7,6 +7,7 @@ import ( "math/big" "github.com/formancehq/go-libs/v2/migrations" + "github.com/formancehq/numscript" "github.com/formancehq/go-libs/v2/bun/bunpaginate" "github.com/formancehq/go-libs/v2/metadata" @@ -297,3 +298,42 @@ func NewListLedgersQuery(pageSize uint64) ListLedgersQuery { PageSize: pageSize, } } + +// numscript rewrite implementation + +var _ numscript.Store = (*numscriptRewriteAdapter)(nil) + +func newNumscriptRewriteAdapter(tx TX) *numscriptRewriteAdapter { + return &numscriptRewriteAdapter{ + TX: tx, + } +} + +type numscriptRewriteAdapter struct { + TX TX +} + +func (s *numscriptRewriteAdapter) GetBalances(ctx context.Context, q numscript.BalanceQuery) (numscript.Balances, error) { + vmBalances, err := s.TX.GetBalances(ctx, BalanceQuery(q)) + if err != nil { + return nil, err + } + return numscript.Balances(vmBalances), nil +} + +func (s *numscriptRewriteAdapter) GetAccountsMetadata(ctx context.Context, q numscript.MetadataQuery) (numscript.AccountsMetadata, error) { + m := numscript.AccountsMetadata{} + + // we ignore the needed metadata values and just return all of them + for address := range q { + v, err := s.TX.GetAccount(ctx, GetAccountQuery{ + Addr: address, + }) + if err != nil { + return nil, err + } + m[v.Address] = v.Metadata + } + + return m, nil +} diff --git a/internal/controller/system/controller.go b/internal/controller/system/controller.go index e721e2ac8..86b6b63d7 100644 --- a/internal/controller/system/controller.go +++ b/internal/controller/system/controller.go @@ -132,6 +132,7 @@ func NewDefaultController(store Store, listener ledgercontroller.Listener, opts store: store, listener: listener, registry: ledgercontroller.NewStateRegistry(), + parser: ledgercontroller.NewDefaultNumscriptParser(), } for _, opt := range append(defaultOptions, opts...) { opt(ret) @@ -172,7 +173,6 @@ func WithEnableFeatures(v bool) Option { } var defaultOptions = []Option{ - WithParser(ledgercontroller.NewDefaultNumscriptParser()), WithMeter(noopmetrics.Meter{}), WithTracer(nooptracer.Tracer{}), } diff --git a/internal/controller/system/module.go b/internal/controller/system/module.go index 18f839583..b8d4b5691 100644 --- a/internal/controller/system/module.go +++ b/internal/controller/system/module.go @@ -18,6 +18,7 @@ type ModuleConfiguration struct { NSCacheConfiguration ledgercontroller.CacheConfiguration DatabaseRetryConfiguration DatabaseRetryConfiguration EnableFeatures bool + NumscriptInterpreter bool } func NewFXModule(configuration ModuleConfiguration) fx.Option { @@ -31,23 +32,25 @@ func NewFXModule(configuration ModuleConfiguration) fx.Option { meterProvider metric.MeterProvider, tracerProvider trace.TracerProvider, ) *DefaultController { - options := make([]Option, 0) + var parser ledgercontroller.NumscriptParser = ledgercontroller.NewDefaultNumscriptParser() + if configuration.NumscriptInterpreter { + parser = ledgercontroller.NewInterpreterNumscriptParser() + } + if configuration.NSCacheConfiguration.MaxCount != 0 { - options = append(options, WithParser(ledgercontroller.NewCachedNumscriptParser( - ledgercontroller.NewDefaultNumscriptParser(), - configuration.NSCacheConfiguration, - ))) + parser = ledgercontroller.NewCachedNumscriptParser(parser, ledgercontroller.CacheConfiguration{ + MaxCount: configuration.NSCacheConfiguration.MaxCount, + }) } return NewDefaultController( store, listener, - append(options, - WithDatabaseRetryConfiguration(configuration.DatabaseRetryConfiguration), - WithMeter(meterProvider.Meter("core")), - WithTracer(tracerProvider.Tracer("core")), - WithEnableFeatures(configuration.EnableFeatures), - )..., + WithParser(parser), + WithDatabaseRetryConfiguration(configuration.DatabaseRetryConfiguration), + WithMeter(meterProvider.Meter("core")), + WithTracer(tracerProvider.Tracer("core")), + WithEnableFeatures(configuration.EnableFeatures), ) }), ) diff --git a/ledger b/ledger new file mode 100755 index 000000000..a7d7905c8 Binary files /dev/null and b/ledger differ diff --git a/openapi.yaml b/openapi.yaml index e0c60b639..4a7d602fe 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -3453,6 +3453,8 @@ components: - IMPORT - TIMEOUT - BULK_SIZE_EXCEEDED + - INTERPRETER_PARSE + - INTERPRETER_RUNTIME example: VALIDATION V2LedgerInfoResponse: type: object diff --git a/openapi/v2.yaml b/openapi/v2.yaml index a06345033..3e2ed140d 100644 --- a/openapi/v2.yaml +++ b/openapi/v2.yaml @@ -14,24 +14,24 @@ paths: operationId: v2GetInfo x-speakeasy-name-override: GetInfo responses: - '200': + "200": description: OK content: application/json: schema: - $ref: '#/components/schemas/V2ConfigInfoResponse' + $ref: "#/components/schemas/V2ConfigInfoResponse" default: description: Error content: application/json: schema: - $ref: '#/components/schemas/V2ErrorResponse' + $ref: "#/components/schemas/V2ErrorResponse" 5XX: description: Error content: application/json: schema: - $ref: '#/components/schemas/V2ErrorResponse' + $ref: "#/components/schemas/V2ErrorResponse" security: - Authorization: - ledger:read @@ -68,18 +68,18 @@ paths: type: string example: aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ== responses: - '200': + "200": description: OK content: application/json: schema: - $ref: '#/components/schemas/V2LedgerListResponse' + $ref: "#/components/schemas/V2LedgerListResponse" default: description: Error content: application/json: schema: - $ref: '#/components/schemas/V2ErrorResponse' + $ref: "#/components/schemas/V2ErrorResponse" security: - Authorization: - ledger:read @@ -99,18 +99,18 @@ paths: tags: - ledger.v2 responses: - '200': + "200": description: OK content: application/json: schema: - $ref: '#/components/schemas/V2GetLedgerResponse' + $ref: "#/components/schemas/V2GetLedgerResponse" default: description: Error content: application/json: schema: - $ref: '#/components/schemas/V2ErrorResponse' + $ref: "#/components/schemas/V2ErrorResponse" security: - Authorization: - ledger:read @@ -124,16 +124,16 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/V2CreateLedgerRequest' + $ref: "#/components/schemas/V2CreateLedgerRequest" responses: - '204': + "204": description: OK default: description: Error content: application/json: schema: - $ref: '#/components/schemas/V2ErrorResponse' + $ref: "#/components/schemas/V2ErrorResponse" security: - Authorization: - ledger:write @@ -156,22 +156,22 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/V2UpdateLedgerMetadataRequest' + $ref: "#/components/schemas/V2UpdateLedgerMetadataRequest" responses: - '204': + "204": description: OK default: description: Error content: application/json: schema: - $ref: '#/components/schemas/V2ErrorResponse' + $ref: "#/components/schemas/V2ErrorResponse" 5XX: description: Error content: application/json: schema: - $ref: '#/components/schemas/V2ErrorResponse' + $ref: "#/components/schemas/V2ErrorResponse" security: - Authorization: - ledger:write @@ -198,14 +198,14 @@ paths: tags: - ledger.v2 responses: - '204': + "204": description: OK default: description: Error content: application/json: schema: - $ref: '#/components/schemas/V2ErrorResponse' + $ref: "#/components/schemas/V2ErrorResponse" security: - Authorization: - ledger:write @@ -225,18 +225,18 @@ paths: type: string example: ledger001 responses: - '200': + "200": description: OK content: application/json: schema: - $ref: '#/components/schemas/V2LedgerInfoResponse' + $ref: "#/components/schemas/V2LedgerInfoResponse" default: description: Error content: application/json: schema: - $ref: '#/components/schemas/V2ErrorResponse' + $ref: "#/components/schemas/V2ErrorResponse" security: - Authorization: - ledger:read @@ -259,26 +259,26 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/V2Bulk' + $ref: "#/components/schemas/V2Bulk" responses: - '200': + "200": description: OK content: application/json: schema: - $ref: '#/components/schemas/V2BulkResponse' - '400': + $ref: "#/components/schemas/V2BulkResponse" + "400": description: OK content: application/json: schema: - $ref: '#/components/schemas/V2BulkResponse' + $ref: "#/components/schemas/V2BulkResponse" default: description: Error content: application/json: schema: - $ref: '#/components/schemas/V2ErrorResponse' + $ref: "#/components/schemas/V2ErrorResponse" security: - Authorization: - ledger:write @@ -310,7 +310,7 @@ paths: type: object additionalProperties: true responses: - '204': + "204": description: OK headers: Count: @@ -323,7 +323,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/V2ErrorResponse' + $ref: "#/components/schemas/V2ErrorResponse" security: - Authorization: - ledger:read @@ -385,18 +385,18 @@ paths: type: object additionalProperties: true responses: - '200': + "200": description: OK content: application/json: schema: - $ref: '#/components/schemas/V2AccountsCursorResponse' + $ref: "#/components/schemas/V2AccountsCursorResponse" default: description: Error content: application/json: schema: - $ref: '#/components/schemas/V2ErrorResponse' + $ref: "#/components/schemas/V2ErrorResponse" security: - Authorization: - ledger:read @@ -443,18 +443,18 @@ paths: type: string format: date-time responses: - '200': + "200": description: OK content: application/json: schema: - $ref: '#/components/schemas/V2AccountResponse' + $ref: "#/components/schemas/V2AccountResponse" default: description: Error content: application/json: schema: - $ref: '#/components/schemas/V2ErrorResponse' + $ref: "#/components/schemas/V2ErrorResponse" security: - Authorization: - ledger:read @@ -506,10 +506,10 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/V2Metadata' + $ref: "#/components/schemas/V2Metadata" required: true responses: - '204': + "204": description: No Content content: {} default: @@ -517,7 +517,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/V2ErrorResponse' + $ref: "#/components/schemas/V2ErrorResponse" security: - Authorization: - ledger:write @@ -559,7 +559,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/V2ErrorResponse' + $ref: "#/components/schemas/V2ErrorResponse" security: - Authorization: - ledger:write @@ -582,18 +582,18 @@ paths: type: string example: ledger001 responses: - '200': + "200": description: OK content: application/json: schema: - $ref: '#/components/schemas/V2StatsResponse' + $ref: "#/components/schemas/V2StatsResponse" default: description: Error content: application/json: schema: - $ref: '#/components/schemas/V2ErrorResponse' + $ref: "#/components/schemas/V2ErrorResponse" security: - Authorization: - ledger:read @@ -625,7 +625,7 @@ paths: type: object additionalProperties: true responses: - '204': + "204": description: OK headers: Count: @@ -638,7 +638,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/V2ErrorResponse' + $ref: "#/components/schemas/V2ErrorResponse" security: - Authorization: - ledger:read @@ -712,18 +712,18 @@ paths: type: object additionalProperties: true responses: - '200': + "200": description: OK content: application/json: schema: - $ref: '#/components/schemas/V2TransactionsCursorResponse' + $ref: "#/components/schemas/V2TransactionsCursorResponse" default: description: Error content: application/json: schema: - $ref: '#/components/schemas/V2ErrorResponse' + $ref: "#/components/schemas/V2ErrorResponse" security: - Authorization: - ledger:read @@ -756,8 +756,7 @@ paths: type: string - name: force in: query - description: - Disable balance checks when passing postings + description: Disable balance checks when passing postings schema: type: boolean example: true @@ -770,20 +769,20 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/V2PostTransaction' + $ref: "#/components/schemas/V2PostTransaction" responses: - '200': + "200": description: OK content: application/json: schema: - $ref: '#/components/schemas/V2CreateTransactionResponse' + $ref: "#/components/schemas/V2CreateTransactionResponse" default: description: Error content: application/json: schema: - $ref: '#/components/schemas/V2ErrorResponse' + $ref: "#/components/schemas/V2ErrorResponse" security: - Authorization: - ledger:write @@ -824,18 +823,18 @@ paths: type: string format: date-time responses: - '200': + "200": description: OK content: application/json: schema: - $ref: '#/components/schemas/V2GetTransactionResponse' + $ref: "#/components/schemas/V2GetTransactionResponse" default: description: Error content: application/json: schema: - $ref: '#/components/schemas/V2ErrorResponse' + $ref: "#/components/schemas/V2ErrorResponse" security: - Authorization: - ledger:read @@ -881,9 +880,9 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/V2Metadata' + $ref: "#/components/schemas/V2Metadata" responses: - '204': + "204": description: No Content content: {} default: @@ -891,7 +890,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/V2ErrorResponse' + $ref: "#/components/schemas/V2ErrorResponse" security: - Authorization: - ledger:write @@ -936,7 +935,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/V2ErrorResponse' + $ref: "#/components/schemas/V2ErrorResponse" security: - Authorization: - ledger:write @@ -985,18 +984,18 @@ paths: type: boolean example: true responses: - '201': + "201": description: OK content: application/json: schema: - $ref: '#/components/schemas/V2RevertTransactionResponse' + $ref: "#/components/schemas/V2RevertTransactionResponse" default: description: Error content: application/json: schema: - $ref: '#/components/schemas/V2ErrorResponse' + $ref: "#/components/schemas/V2ErrorResponse" security: - Authorization: - ledger:write @@ -1034,18 +1033,18 @@ paths: type: object additionalProperties: true responses: - '200': + "200": description: OK content: application/json: schema: - $ref: '#/components/schemas/V2AggregateBalancesResponse' + $ref: "#/components/schemas/V2AggregateBalancesResponse" default: description: Error content: application/json: schema: - $ref: '#/components/schemas/V2ErrorResponse' + $ref: "#/components/schemas/V2ErrorResponse" security: - Authorization: - ledger:read @@ -1122,18 +1121,18 @@ paths: type: object additionalProperties: true responses: - '200': + "200": description: OK content: application/json: schema: - $ref: '#/components/schemas/V2VolumesWithBalanceCursorResponse' + $ref: "#/components/schemas/V2VolumesWithBalanceCursorResponse" default: description: Error content: application/json: schema: - $ref: '#/components/schemas/V2ErrorResponse' + $ref: "#/components/schemas/V2ErrorResponse" security: - Authorization: - ledger:read @@ -1190,18 +1189,18 @@ paths: type: object additionalProperties: true responses: - '200': + "200": description: OK content: application/json: schema: - $ref: '#/components/schemas/V2LogsCursorResponse' + $ref: "#/components/schemas/V2LogsCursorResponse" default: description: Error content: application/json: schema: - $ref: '#/components/schemas/V2ErrorResponse' + $ref: "#/components/schemas/V2ErrorResponse" security: - Authorization: - ledger:read @@ -1225,14 +1224,14 @@ paths: schema: type: string responses: - '204': + "204": description: Import OK default: description: Error content: application/json: schema: - $ref: '#/components/schemas/V2ErrorResponse' + $ref: "#/components/schemas/V2ErrorResponse" security: - Authorization: - ledger:write @@ -1252,7 +1251,7 @@ paths: type: string example: ledger001 responses: - '200': + "200": description: Import OK default: description: Error @@ -1271,9 +1270,9 @@ components: type: oauth2 flows: clientCredentials: - tokenUrl: '/api/auth/oauth/token' - refreshUrl: '/api/auth/oauth/token' - scopes: { } + tokenUrl: "/api/auth/oauth/token" + refreshUrl: "/api/auth/oauth/token" + scopes: {} schemas: V2AccountsCursorResponse: @@ -1302,11 +1301,11 @@ components: example: YXVsdCBhbmQgYSBtYXhpbXVtIG1heF9yZXN1bHRzLol= next: type: string - example: '' + example: "" data: type: array items: - $ref: '#/components/schemas/V2Account' + $ref: "#/components/schemas/V2Account" V2TransactionsCursorResponse: type: object required: @@ -1333,11 +1332,11 @@ components: example: YXVsdCBhbmQgYSBtYXhpbXVtIG1heF9yZXN1bHRzLol= next: type: string - example: '' + example: "" data: type: array items: - $ref: '#/components/schemas/V2Transaction' + $ref: "#/components/schemas/V2Transaction" V2LogsCursorResponse: type: object required: @@ -1364,25 +1363,25 @@ components: example: YXVsdCBhbmQgYSBtYXhpbXVtIG1heF9yZXN1bHRzLol= next: type: string - example: '' + example: "" data: type: array items: - $ref: '#/components/schemas/V2Log' + $ref: "#/components/schemas/V2Log" V2AccountResponse: type: object required: - data properties: data: - $ref: '#/components/schemas/V2Account' + $ref: "#/components/schemas/V2Account" V2AggregateBalancesResponse: type: object required: - data properties: data: - $ref: '#/components/schemas/V2AssetsBalances' + $ref: "#/components/schemas/V2AssetsBalances" V2VolumesWithBalanceCursorResponse: type: object required: @@ -1409,11 +1408,11 @@ components: example: YXVsdCBhbmQgYSBtYXhpbXVtIG1heF9yZXN1bHRzLol= next: type: string - example: '' + example: "" data: type: array items: - $ref: '#/components/schemas/V2VolumesWithBalance' + $ref: "#/components/schemas/V2VolumesWithBalance" V2VolumesWithBalance: type: object properties: @@ -1441,7 +1440,7 @@ components: additionalProperties: type: string example: - admin: 'true' + admin: "true" V2ConfigInfo: type: object properties: @@ -1468,11 +1467,11 @@ components: additionalProperties: type: string example: - admin: 'true' + admin: "true" volumes: - $ref: '#/components/schemas/V2Volumes' + $ref: "#/components/schemas/V2Volumes" effectiveVolumes: - $ref: '#/components/schemas/V2Volumes' + $ref: "#/components/schemas/V2Volumes" V2AssetsBalances: type: object additionalProperties: @@ -1515,12 +1514,12 @@ components: postings: type: array items: - $ref: '#/components/schemas/V2Posting' + $ref: "#/components/schemas/V2Posting" reference: type: string example: ref:001 metadata: - $ref: '#/components/schemas/V2Metadata' + $ref: "#/components/schemas/V2Metadata" id: type: integer format: bigint @@ -1531,13 +1530,13 @@ components: type: string format: date-time preCommitVolumes: - $ref: '#/components/schemas/V2AggregatedVolumes' + $ref: "#/components/schemas/V2AggregatedVolumes" postCommitVolumes: - $ref: '#/components/schemas/V2AggregatedVolumes' + $ref: "#/components/schemas/V2AggregatedVolumes" preCommitEffectiveVolumes: - $ref: '#/components/schemas/V2AggregatedVolumes' + $ref: "#/components/schemas/V2AggregatedVolumes" postCommitEffectiveVolumes: - $ref: '#/components/schemas/V2AggregatedVolumes' + $ref: "#/components/schemas/V2AggregatedVolumes" required: - postings - timestamp @@ -1556,7 +1555,7 @@ components: postings: type: array items: - $ref: '#/components/schemas/V2Posting' + $ref: "#/components/schemas/V2Posting" script: type: object properties: @@ -1575,7 +1574,7 @@ components: type: string example: ref:001 metadata: - $ref: '#/components/schemas/V2Metadata' + $ref: "#/components/schemas/V2Metadata" V2Stats: type: object properties: @@ -1623,28 +1622,28 @@ components: V2CreateTransactionResponse: properties: data: - $ref: '#/components/schemas/V2Transaction' + $ref: "#/components/schemas/V2Transaction" type: object required: - data V2RevertTransactionResponse: - $ref: '#/components/schemas/V2CreateTransactionResponse' + $ref: "#/components/schemas/V2CreateTransactionResponse" V2GetTransactionResponse: properties: data: - $ref: '#/components/schemas/V2Transaction' + $ref: "#/components/schemas/V2Transaction" type: object required: - data V2StatsResponse: properties: data: - $ref: '#/components/schemas/V2Stats' + $ref: "#/components/schemas/V2Stats" type: object required: - data V2ConfigInfoResponse: - $ref: '#/components/schemas/V2ConfigInfo' + $ref: "#/components/schemas/V2ConfigInfo" V2Volume: type: object properties: @@ -1667,7 +1666,7 @@ components: V2Volumes: type: object additionalProperties: - $ref: '#/components/schemas/V2Volume' + $ref: "#/components/schemas/V2Volume" example: USD: input: 100 @@ -1680,7 +1679,7 @@ components: V2AggregatedVolumes: type: object additionalProperties: - $ref: '#/components/schemas/V2Volumes' + $ref: "#/components/schemas/V2Volumes" example: orders:1: USD: @@ -1699,10 +1698,10 @@ components: - errorMessage properties: errorCode: - $ref: '#/components/schemas/V2ErrorsEnum' + $ref: "#/components/schemas/V2ErrorsEnum" errorMessage: type: string - example: '[VALIDATION] invalid ''cursor'' query param' + example: "[VALIDATION] invalid 'cursor' query param" details: type: string example: >- @@ -1724,12 +1723,14 @@ components: - IMPORT - TIMEOUT - BULK_SIZE_EXCEEDED + - INTERPRETER_PARSE + - INTERPRETER_RUNTIME example: VALIDATION V2LedgerInfoResponse: type: object properties: data: - $ref: '#/components/schemas/V2LedgerInfo' + $ref: "#/components/schemas/V2LedgerInfo" V2LedgerInfo: type: object properties: @@ -1742,7 +1743,7 @@ components: migrations: type: array items: - $ref: '#/components/schemas/V2MigrationInfo' + $ref: "#/components/schemas/V2MigrationInfo" V2MigrationInfo: type: object properties: @@ -1765,7 +1766,7 @@ components: V2Bulk: type: array items: - $ref: '#/components/schemas/V2BulkElement' + $ref: "#/components/schemas/V2BulkElement" V2BaseBulkElement: type: object required: @@ -1778,25 +1779,25 @@ components: V2BulkElement: type: object oneOf: - - $ref: '#/components/schemas/V2BulkElementCreateTransaction' - - $ref: '#/components/schemas/V2BulkElementAddMetadata' - - $ref: '#/components/schemas/V2BulkElementRevertTransaction' - - $ref: '#/components/schemas/V2BulkElementDeleteMetadata' + - $ref: "#/components/schemas/V2BulkElementCreateTransaction" + - $ref: "#/components/schemas/V2BulkElementAddMetadata" + - $ref: "#/components/schemas/V2BulkElementRevertTransaction" + - $ref: "#/components/schemas/V2BulkElementDeleteMetadata" discriminator: propertyName: action mapping: - CREATE_TRANSACTION: '#/components/schemas/V2BulkElementCreateTransaction' - ADD_METADATA: '#/components/schemas/V2BulkElementAddMetadata' - REVERT_TRANSACTION: '#/components/schemas/V2BulkElementRevertTransaction' - DELETE_METADATA: '#/components/schemas/V2BulkElementDeleteMetadata' + CREATE_TRANSACTION: "#/components/schemas/V2BulkElementCreateTransaction" + ADD_METADATA: "#/components/schemas/V2BulkElementAddMetadata" + REVERT_TRANSACTION: "#/components/schemas/V2BulkElementRevertTransaction" + DELETE_METADATA: "#/components/schemas/V2BulkElementDeleteMetadata" V2BulkElementCreateTransaction: type: object allOf: - - $ref: '#/components/schemas/V2BaseBulkElement' + - $ref: "#/components/schemas/V2BaseBulkElement" - type: object properties: data: - $ref: '#/components/schemas/V2PostTransaction' + $ref: "#/components/schemas/V2PostTransaction" V2TargetId: oneOf: - type: string @@ -1810,16 +1811,16 @@ components: V2BulkElementAddMetadata: type: object allOf: - - $ref: '#/components/schemas/V2BaseBulkElement' + - $ref: "#/components/schemas/V2BaseBulkElement" - type: object properties: data: type: object properties: targetId: - $ref: '#/components/schemas/V2TargetId' + $ref: "#/components/schemas/V2TargetId" targetType: - $ref: '#/components/schemas/V2TargetType' + $ref: "#/components/schemas/V2TargetType" metadata: type: object additionalProperties: @@ -1831,7 +1832,7 @@ components: V2BulkElementRevertTransaction: type: object allOf: - - $ref: '#/components/schemas/V2BaseBulkElement' + - $ref: "#/components/schemas/V2BaseBulkElement" - type: object properties: data: @@ -1849,16 +1850,16 @@ components: V2BulkElementDeleteMetadata: type: object allOf: - - $ref: '#/components/schemas/V2BaseBulkElement' + - $ref: "#/components/schemas/V2BaseBulkElement" - type: object properties: data: type: object properties: targetId: - $ref: '#/components/schemas/V2TargetId' + $ref: "#/components/schemas/V2TargetId" targetType: - $ref: '#/components/schemas/V2TargetType' + $ref: "#/components/schemas/V2TargetType" key: type: string required: @@ -1870,26 +1871,26 @@ components: data: type: array items: - $ref: '#/components/schemas/V2BulkElementResult' + $ref: "#/components/schemas/V2BulkElementResult" type: object required: - data V2BulkElementResult: type: object oneOf: - - $ref: '#/components/schemas/V2BulkElementResultCreateTransaction' - - $ref: '#/components/schemas/V2BulkElementResultAddMetadata' - - $ref: '#/components/schemas/V2BulkElementResultRevertTransaction' - - $ref: '#/components/schemas/V2BulkElementResultDeleteMetadata' - - $ref: '#/components/schemas/V2BulkElementResultError' + - $ref: "#/components/schemas/V2BulkElementResultCreateTransaction" + - $ref: "#/components/schemas/V2BulkElementResultAddMetadata" + - $ref: "#/components/schemas/V2BulkElementResultRevertTransaction" + - $ref: "#/components/schemas/V2BulkElementResultDeleteMetadata" + - $ref: "#/components/schemas/V2BulkElementResultError" discriminator: propertyName: responseType mapping: - CREATE_TRANSACTION: '#/components/schemas/V2BulkElementResultCreateTransaction' - ADD_METADATA: '#/components/schemas/V2BulkElementResultAddMetadata' - REVERT_TRANSACTION: '#/components/schemas/V2BulkElementResultRevertTransaction' - DELETE_METADATA: '#/components/schemas/V2BulkElementResultDeleteMetadata' - ERROR: '#/components/schemas/V2BulkElementResultError' + CREATE_TRANSACTION: "#/components/schemas/V2BulkElementResultCreateTransaction" + ADD_METADATA: "#/components/schemas/V2BulkElementResultAddMetadata" + REVERT_TRANSACTION: "#/components/schemas/V2BulkElementResultRevertTransaction" + DELETE_METADATA: "#/components/schemas/V2BulkElementResultDeleteMetadata" + ERROR: "#/components/schemas/V2BulkElementResultError" V2BaseBulkElementResult: type: object properties: @@ -1899,31 +1900,31 @@ components: - responseType V2BulkElementResultCreateTransaction: allOf: - - $ref: '#/components/schemas/V2BaseBulkElementResult' + - $ref: "#/components/schemas/V2BaseBulkElementResult" - type: object properties: data: - $ref: '#/components/schemas/V2Transaction' + $ref: "#/components/schemas/V2Transaction" required: - data V2BulkElementResultAddMetadata: allOf: - - $ref: '#/components/schemas/V2BaseBulkElementResult' + - $ref: "#/components/schemas/V2BaseBulkElementResult" V2BulkElementResultRevertTransaction: allOf: - - $ref: '#/components/schemas/V2BaseBulkElementResult' + - $ref: "#/components/schemas/V2BaseBulkElementResult" - type: object properties: data: - $ref: '#/components/schemas/V2Transaction' + $ref: "#/components/schemas/V2Transaction" required: - data V2BulkElementResultDeleteMetadata: allOf: - - $ref: '#/components/schemas/V2BaseBulkElementResult' + - $ref: "#/components/schemas/V2BaseBulkElementResult" V2BulkElementResultError: allOf: - - $ref: '#/components/schemas/V2BaseBulkElementResult' + - $ref: "#/components/schemas/V2BaseBulkElementResult" - type: object properties: errorCode: @@ -1941,7 +1942,7 @@ components: bucket: type: string metadata: - $ref: '#/components/schemas/V2Metadata' + $ref: "#/components/schemas/V2Metadata" features: type: object additionalProperties: @@ -1957,7 +1958,7 @@ components: bucket: type: string metadata: - $ref: '#/components/schemas/V2Metadata' + $ref: "#/components/schemas/V2Metadata" required: - name - addedAt @@ -1988,17 +1989,17 @@ components: example: YXVsdCBhbmQgYSBtYXhpbXVtIG1heF9yZXN1bHRzLol= next: type: string - example: '' + example: "" data: type: array items: - $ref: '#/components/schemas/V2Ledger' + $ref: "#/components/schemas/V2Ledger" V2UpdateLedgerMetadataRequest: - $ref: '#/components/schemas/V2Metadata' + $ref: "#/components/schemas/V2Metadata" V2GetLedgerResponse: type: object required: - data properties: data: - $ref: '#/components/schemas/V2Ledger' + $ref: "#/components/schemas/V2Ledger" diff --git a/pkg/client/.speakeasy/gen.lock b/pkg/client/.speakeasy/gen.lock index 7db1bbd33..3ac7e688a 100644 --- a/pkg/client/.speakeasy/gen.lock +++ b/pkg/client/.speakeasy/gen.lock @@ -1,12 +1,12 @@ lockVersion: 2.0.0 id: a9ac79e1-e429-4ee3-96c4-ec973f19bec3 management: - docChecksum: f87e5e30078da93c83bec4115056a7a9 + docChecksum: 043615f02d6da6be8aa8e5c006587e66 docVersion: v1 speakeasyVersion: 1.351.0 generationVersion: 2.384.1 - releaseVersion: 0.4.15 - configChecksum: 82b92b8a70bc4a520560afe9e887834c + releaseVersion: 0.4.16 + configChecksum: 87ddc5f3dd8be9290d0bf5cabd849a48 features: go: additionalDependencies: 0.1.0 diff --git a/pkg/client/.speakeasy/gen.yaml b/pkg/client/.speakeasy/gen.yaml index ad5852e79..50198e28b 100644 --- a/pkg/client/.speakeasy/gen.yaml +++ b/pkg/client/.speakeasy/gen.yaml @@ -15,7 +15,7 @@ generation: auth: oAuth2ClientCredentialsEnabled: true go: - version: 0.4.15 + version: 0.4.16 additionalDependencies: {} allowUnknownFieldsInWeakUnions: false clientServerStatusCodesAsErrors: true diff --git a/pkg/client/docs/models/components/v2errorsenum.md b/pkg/client/docs/models/components/v2errorsenum.md index a292606a7..35dc2f95a 100644 --- a/pkg/client/docs/models/components/v2errorsenum.md +++ b/pkg/client/docs/models/components/v2errorsenum.md @@ -3,19 +3,21 @@ ## Values -| Name | Value | -| ------------------------------- | ------------------------------- | -| `V2ErrorsEnumInternal` | INTERNAL | -| `V2ErrorsEnumInsufficientFund` | INSUFFICIENT_FUND | -| `V2ErrorsEnumValidation` | VALIDATION | -| `V2ErrorsEnumConflict` | CONFLICT | -| `V2ErrorsEnumCompilationFailed` | COMPILATION_FAILED | -| `V2ErrorsEnumMetadataOverride` | METADATA_OVERRIDE | -| `V2ErrorsEnumNotFound` | NOT_FOUND | -| `V2ErrorsEnumRevertOccurring` | REVERT_OCCURRING | -| `V2ErrorsEnumAlreadyRevert` | ALREADY_REVERT | -| `V2ErrorsEnumNoPostings` | NO_POSTINGS | -| `V2ErrorsEnumLedgerNotFound` | LEDGER_NOT_FOUND | -| `V2ErrorsEnumImport` | IMPORT | -| `V2ErrorsEnumTimeout` | TIMEOUT | -| `V2ErrorsEnumBulkSizeExceeded` | BULK_SIZE_EXCEEDED | \ No newline at end of file +| Name | Value | +| -------------------------------- | -------------------------------- | +| `V2ErrorsEnumInternal` | INTERNAL | +| `V2ErrorsEnumInsufficientFund` | INSUFFICIENT_FUND | +| `V2ErrorsEnumValidation` | VALIDATION | +| `V2ErrorsEnumConflict` | CONFLICT | +| `V2ErrorsEnumCompilationFailed` | COMPILATION_FAILED | +| `V2ErrorsEnumMetadataOverride` | METADATA_OVERRIDE | +| `V2ErrorsEnumNotFound` | NOT_FOUND | +| `V2ErrorsEnumRevertOccurring` | REVERT_OCCURRING | +| `V2ErrorsEnumAlreadyRevert` | ALREADY_REVERT | +| `V2ErrorsEnumNoPostings` | NO_POSTINGS | +| `V2ErrorsEnumLedgerNotFound` | LEDGER_NOT_FOUND | +| `V2ErrorsEnumImport` | IMPORT | +| `V2ErrorsEnumTimeout` | TIMEOUT | +| `V2ErrorsEnumBulkSizeExceeded` | BULK_SIZE_EXCEEDED | +| `V2ErrorsEnumInterpreterParse` | INTERPRETER_PARSE | +| `V2ErrorsEnumInterpreterRuntime` | INTERPRETER_RUNTIME | \ No newline at end of file diff --git a/pkg/client/formance.go b/pkg/client/formance.go index e1a4b7c0c..949d62ce8 100644 --- a/pkg/client/formance.go +++ b/pkg/client/formance.go @@ -143,9 +143,9 @@ func New(opts ...SDKOption) *Formance { sdkConfiguration: sdkConfiguration{ Language: "go", OpenAPIDocVersion: "v1", - SDKVersion: "0.4.15", + SDKVersion: "0.4.16", GenVersion: "2.384.1", - UserAgent: "speakeasy-sdk/go 0.4.15 2.384.1 v1 github.com/formancehq/stack/ledger/client", + UserAgent: "speakeasy-sdk/go 0.4.16 2.384.1 v1 github.com/formancehq/stack/ledger/client", Hooks: hooks.New(), }, } diff --git a/pkg/client/models/components/v2errorsenum.go b/pkg/client/models/components/v2errorsenum.go index 598f3248c..862ca3c52 100644 --- a/pkg/client/models/components/v2errorsenum.go +++ b/pkg/client/models/components/v2errorsenum.go @@ -10,20 +10,22 @@ import ( type V2ErrorsEnum string const ( - V2ErrorsEnumInternal V2ErrorsEnum = "INTERNAL" - V2ErrorsEnumInsufficientFund V2ErrorsEnum = "INSUFFICIENT_FUND" - V2ErrorsEnumValidation V2ErrorsEnum = "VALIDATION" - V2ErrorsEnumConflict V2ErrorsEnum = "CONFLICT" - V2ErrorsEnumCompilationFailed V2ErrorsEnum = "COMPILATION_FAILED" - V2ErrorsEnumMetadataOverride V2ErrorsEnum = "METADATA_OVERRIDE" - V2ErrorsEnumNotFound V2ErrorsEnum = "NOT_FOUND" - V2ErrorsEnumRevertOccurring V2ErrorsEnum = "REVERT_OCCURRING" - V2ErrorsEnumAlreadyRevert V2ErrorsEnum = "ALREADY_REVERT" - V2ErrorsEnumNoPostings V2ErrorsEnum = "NO_POSTINGS" - V2ErrorsEnumLedgerNotFound V2ErrorsEnum = "LEDGER_NOT_FOUND" - V2ErrorsEnumImport V2ErrorsEnum = "IMPORT" - V2ErrorsEnumTimeout V2ErrorsEnum = "TIMEOUT" - V2ErrorsEnumBulkSizeExceeded V2ErrorsEnum = "BULK_SIZE_EXCEEDED" + V2ErrorsEnumInternal V2ErrorsEnum = "INTERNAL" + V2ErrorsEnumInsufficientFund V2ErrorsEnum = "INSUFFICIENT_FUND" + V2ErrorsEnumValidation V2ErrorsEnum = "VALIDATION" + V2ErrorsEnumConflict V2ErrorsEnum = "CONFLICT" + V2ErrorsEnumCompilationFailed V2ErrorsEnum = "COMPILATION_FAILED" + V2ErrorsEnumMetadataOverride V2ErrorsEnum = "METADATA_OVERRIDE" + V2ErrorsEnumNotFound V2ErrorsEnum = "NOT_FOUND" + V2ErrorsEnumRevertOccurring V2ErrorsEnum = "REVERT_OCCURRING" + V2ErrorsEnumAlreadyRevert V2ErrorsEnum = "ALREADY_REVERT" + V2ErrorsEnumNoPostings V2ErrorsEnum = "NO_POSTINGS" + V2ErrorsEnumLedgerNotFound V2ErrorsEnum = "LEDGER_NOT_FOUND" + V2ErrorsEnumImport V2ErrorsEnum = "IMPORT" + V2ErrorsEnumTimeout V2ErrorsEnum = "TIMEOUT" + V2ErrorsEnumBulkSizeExceeded V2ErrorsEnum = "BULK_SIZE_EXCEEDED" + V2ErrorsEnumInterpreterParse V2ErrorsEnum = "INTERPRETER_PARSE" + V2ErrorsEnumInterpreterRuntime V2ErrorsEnum = "INTERPRETER_RUNTIME" ) func (e V2ErrorsEnum) ToPointer() *V2ErrorsEnum { @@ -62,6 +64,10 @@ func (e *V2ErrorsEnum) UnmarshalJSON(data []byte) error { case "TIMEOUT": fallthrough case "BULK_SIZE_EXCEEDED": + fallthrough + case "INTERPRETER_PARSE": + fallthrough + case "INTERPRETER_RUNTIME": *e = V2ErrorsEnum(v) return nil default: diff --git a/pkg/testserver/server.go b/pkg/testserver/server.go index 2939de5b1..c70670e81 100644 --- a/pkg/testserver/server.go +++ b/pkg/testserver/server.go @@ -3,16 +3,17 @@ package testserver import ( "context" "fmt" + "io" + "net/http" + "strings" + "time" + "github.com/formancehq/go-libs/v2/otlp" "github.com/formancehq/go-libs/v2/otlp/otlpmetrics" "github.com/formancehq/go-libs/v2/publish" "github.com/google/uuid" "github.com/nats-io/nats.go" "github.com/uptrace/bun" - "io" - "net/http" - "strings" - "time" "github.com/formancehq/go-libs/v2/bun/bunconnect" "github.com/formancehq/go-libs/v2/httpclient" @@ -37,13 +38,14 @@ type OTLPConfig struct { } type Configuration struct { - PostgresConfiguration bunconnect.ConnectionOptions - NatsURL string - Output io.Writer - Debug bool - OTLPConfig *OTLPConfig - ExperimentalFeatures bool - BulkMaxSize int + PostgresConfiguration bunconnect.ConnectionOptions + NatsURL string + Output io.Writer + Debug bool + OTLPConfig *OTLPConfig + ExperimentalFeatures bool + BulkMaxSize int + ExperimentalNumscriptRewrite bool } type Server struct { @@ -81,6 +83,12 @@ func (s *Server) Start() { fmt.Sprint(s.configuration.BulkMaxSize), ) } + if s.configuration.ExperimentalNumscriptRewrite { + args = append( + args, + "--"+cmd.NumscriptInterpreterFlag, + ) + } if s.configuration.PostgresConfiguration.MaxIdleConns != 0 { args = append( args, diff --git a/test/e2e/api_bulk.go b/test/e2e/api_bulk.go index 7c6570312..4dfe1c918 100644 --- a/test/e2e/api_bulk.go +++ b/test/e2e/api_bulk.go @@ -3,13 +3,14 @@ package test_suite import ( + "math/big" + "time" + "github.com/formancehq/go-libs/v2/logging" . "github.com/formancehq/go-libs/v2/testing/api" . "github.com/formancehq/ledger/pkg/testserver" "github.com/formancehq/stack/ledger/client/models/components" "github.com/formancehq/stack/ledger/client/models/operations" - "math/big" - "time" "github.com/formancehq/go-libs/v2/metadata" . "github.com/onsi/ginkgo/v2" @@ -17,173 +18,192 @@ import ( ) var _ = Context("Ledger engine tests", func() { - var ( - db = UseTemplatedDatabase() - ctx = logging.TestingContext() - bulkMaxSize = 5 - ) - testServer := NewTestServer(func() Configuration { - return Configuration{ - PostgresConfiguration: db.GetValue().ConnectionOptions(), - Output: GinkgoWriter, - Debug: debug, - NatsURL: natsServer.GetValue().ClientURL(), - BulkMaxSize: bulkMaxSize, - } - }) - BeforeEach(func() { - err := CreateLedger(ctx, testServer.GetValue(), operations.V2CreateLedgerRequest{ - Ledger: "default", - }) - Expect(err).To(BeNil()) - }) - When("creating a bulk on a ledger", func() { - var ( - now = time.Now().Round(time.Microsecond).UTC() - items []components.V2BulkElement - err error - ) - BeforeEach(func() { - items = []components.V2BulkElement{ - components.CreateV2BulkElementCreateTransaction(components.V2BulkElementCreateTransaction{ - Data: &components.V2PostTransaction{ - Metadata: map[string]string{}, + for _, data := range []struct { + description string + numscriptRewrite bool + }{ + {"default", false}, + {"numscript rewrite", true}, + } { + + Context(data.description, func() { + var ( + db = UseTemplatedDatabase() + ctx = logging.TestingContext() + bulkMaxSize = 5 + ) + + testServer := NewTestServer(func() Configuration { + return Configuration{ + PostgresConfiguration: db.GetValue().ConnectionOptions(), + Output: GinkgoWriter, + Debug: debug, + NatsURL: natsServer.GetValue().ClientURL(), + BulkMaxSize: bulkMaxSize, + ExperimentalNumscriptRewrite: data.numscriptRewrite, + } + }) + BeforeEach(func() { + err := CreateLedger(ctx, testServer.GetValue(), operations.V2CreateLedgerRequest{ + Ledger: "default", + }) + Expect(err).To(BeNil()) + }) + When("creating a bulk on a ledger", func() { + var ( + now = time.Now().Round(time.Microsecond).UTC() + items []components.V2BulkElement + err error + ) + BeforeEach(func() { + items = []components.V2BulkElement{ + components.CreateV2BulkElementCreateTransaction(components.V2BulkElementCreateTransaction{ + Data: &components.V2PostTransaction{ + Metadata: map[string]string{}, + Postings: []components.V2Posting{{ + Amount: big.NewInt(100), + Asset: "USD/2", + Destination: "bank", + Source: "world", + }}, + Timestamp: &now, + }, + }), + components.CreateV2BulkElementAddMetadata(components.V2BulkElementAddMetadata{ + Data: &components.Data{ + Metadata: metadata.Metadata{ + "foo": "bar", + "role": "admin", + }, + TargetID: components.CreateV2TargetIDBigint(big.NewInt(1)), + TargetType: components.V2TargetTypeTransaction, + }, + }), + components.CreateV2BulkElementDeleteMetadata(components.V2BulkElementDeleteMetadata{ + Data: &components.V2BulkElementDeleteMetadataData{ + Key: "foo", + TargetID: components.CreateV2TargetIDBigint(big.NewInt(1)), + TargetType: components.V2TargetTypeTransaction, + }, + }), + components.CreateV2BulkElementRevertTransaction(components.V2BulkElementRevertTransaction{ + Data: &components.V2BulkElementRevertTransactionData{ + ID: big.NewInt(1), + }, + }), + } + }) + JustBeforeEach(func() { + _, err = CreateBulk(ctx, testServer.GetValue(), operations.V2CreateBulkRequest{ + RequestBody: items, + Ledger: "default", + }) + }) + It("should be ok", func() { + Expect(err).To(Succeed()) + + tx, err := GetTransaction(ctx, testServer.GetValue(), operations.V2GetTransactionRequest{ + ID: big.NewInt(1), + Ledger: "default", + }) + Expect(err).To(Succeed()) + reversedTx, err := GetTransaction(ctx, testServer.GetValue(), operations.V2GetTransactionRequest{ + ID: big.NewInt(2), + Ledger: "default", + }) + Expect(err).To(Succeed()) + + Expect(*tx).To(Equal(components.V2Transaction{ + ID: big.NewInt(1), + Metadata: metadata.Metadata{ + "role": "admin", + }, Postings: []components.V2Posting{{ Amount: big.NewInt(100), Asset: "USD/2", Destination: "bank", Source: "world", }}, - Timestamp: &now, - }, - }), - components.CreateV2BulkElementAddMetadata(components.V2BulkElementAddMetadata{ - Data: &components.Data{ - Metadata: metadata.Metadata{ - "foo": "bar", - "role": "admin", - }, - TargetID: components.CreateV2TargetIDBigint(big.NewInt(1)), - TargetType: components.V2TargetTypeTransaction, - }, - }), - components.CreateV2BulkElementDeleteMetadata(components.V2BulkElementDeleteMetadata{ - Data: &components.V2BulkElementDeleteMetadataData{ - Key: "foo", - TargetID: components.CreateV2TargetIDBigint(big.NewInt(1)), - TargetType: components.V2TargetTypeTransaction, - }, - }), - components.CreateV2BulkElementRevertTransaction(components.V2BulkElementRevertTransaction{ - Data: &components.V2BulkElementRevertTransactionData{ - ID: big.NewInt(1), - }, - }), - } - }) - JustBeforeEach(func() { - _, err = CreateBulk(ctx, testServer.GetValue(), operations.V2CreateBulkRequest{ - RequestBody: items, - Ledger: "default", - }) - }) - It("should be ok", func() { - Expect(err).To(Succeed()) - - tx, err := GetTransaction(ctx, testServer.GetValue(), operations.V2GetTransactionRequest{ - ID: big.NewInt(1), - Ledger: "default", - }) - Expect(err).To(Succeed()) - - reversedTx, err := GetTransaction(ctx, testServer.GetValue(), operations.V2GetTransactionRequest{ - ID: big.NewInt(2), - Ledger: "default", - }) - Expect(err).To(Succeed()) - - Expect(*tx).To(Equal(components.V2Transaction{ - ID: big.NewInt(1), - Metadata: metadata.Metadata{ - "role": "admin", - }, - Postings: []components.V2Posting{{ - Amount: big.NewInt(100), - Asset: "USD/2", - Destination: "bank", - Source: "world", - }}, - Reverted: true, - RevertedAt: &reversedTx.Timestamp, - Timestamp: now, - InsertedAt: tx.InsertedAt, - })) - }) - Context("with exceeded batch size", func() { - BeforeEach(func() { - items = make([]components.V2BulkElement, 0) - for i := 0; i < bulkMaxSize+1; i++ { - items = append(items, components.CreateV2BulkElementCreateTransaction(components.V2BulkElementCreateTransaction{ - Data: &components.V2PostTransaction{ - Metadata: map[string]string{}, - Postings: []components.V2Posting{{ - Amount: big.NewInt(100), - Asset: "USD/2", - Destination: "bank", - Source: "world", - }}, - Timestamp: &now, - }, + Reverted: true, + RevertedAt: &reversedTx.Timestamp, + Timestamp: now, + InsertedAt: tx.InsertedAt, })) - } - }) - It("should respond with an error", func() { - Expect(err).To(HaveErrorCode(string(components.V2ErrorsEnumBulkSizeExceeded))) + }) + Context("with exceeded batch size", func() { + BeforeEach(func() { + items = make([]components.V2BulkElement, 0) + for i := 0; i < bulkMaxSize+1; i++ { + items = append(items, components.CreateV2BulkElementCreateTransaction(components.V2BulkElementCreateTransaction{ + Data: &components.V2PostTransaction{ + Metadata: map[string]string{}, + Postings: []components.V2Posting{{ + Amount: big.NewInt(100), + Asset: "USD/2", + Destination: "bank", + Source: "world", + }}, + Timestamp: &now, + }, + })) + } + }) + It("should respond with an error", func() { + Expect(err).To(HaveErrorCode(string(components.V2ErrorsEnumBulkSizeExceeded))) + }) + }) }) - }) - }) - When("creating a bulk with an error on a ledger", func() { - var ( - now = time.Now().Round(time.Microsecond).UTC() - err error - bulkResponse []components.V2BulkElementResult - ) - BeforeEach(func() { - bulkResponse, err = CreateBulk(ctx, testServer.GetValue(), operations.V2CreateBulkRequest{ - RequestBody: []components.V2BulkElement{ - components.CreateV2BulkElementCreateTransaction(components.V2BulkElementCreateTransaction{ - Data: &components.V2PostTransaction{ - Metadata: map[string]string{}, - Postings: []components.V2Posting{{ - Amount: big.NewInt(100), - Asset: "USD/2", - Destination: "bank", - Source: "world", - }}, - Timestamp: &now, - }, - }), - components.CreateV2BulkElementCreateTransaction(components.V2BulkElementCreateTransaction{ - Data: &components.V2PostTransaction{ - Metadata: map[string]string{}, - Postings: []components.V2Posting{{ - Amount: big.NewInt(200), // Insufficient fund - Asset: "USD/2", - Destination: "user", - Source: "bank", - }}, - Timestamp: &now, + When("creating a bulk with an error on a ledger", func() { + var ( + now = time.Now().Round(time.Microsecond).UTC() + err error + bulkResponse []components.V2BulkElementResult + ) + BeforeEach(func() { + bulkResponse, err = CreateBulk(ctx, testServer.GetValue(), operations.V2CreateBulkRequest{ + RequestBody: []components.V2BulkElement{ + components.CreateV2BulkElementCreateTransaction(components.V2BulkElementCreateTransaction{ + Data: &components.V2PostTransaction{ + Metadata: map[string]string{}, + Postings: []components.V2Posting{{ + Amount: big.NewInt(100), + Asset: "USD/2", + Destination: "bank", + Source: "world", + }}, + Timestamp: &now, + }, + }), + components.CreateV2BulkElementCreateTransaction(components.V2BulkElementCreateTransaction{ + Data: &components.V2PostTransaction{ + Metadata: map[string]string{}, + Postings: []components.V2Posting{{ + Amount: big.NewInt(200), // Insufficient fund + Asset: "USD/2", + Destination: "user", + Source: "bank", + }}, + Timestamp: &now, + }, + }), }, - }), - }, - Ledger: "default", + Ledger: "default", + }) + Expect(err).To(Succeed()) + }) + It("should respond with an error", func() { + var expectedErr string + if data.numscriptRewrite { + expectedErr = "INTERPRETER_RUNTIME" + } else { + expectedErr = "INSUFFICIENT_FUND" + } + Expect(bulkResponse[1].Type).To(Equal(components.V2BulkElementResultType("ERROR"))) + Expect(bulkResponse[1].V2BulkElementResultError.ErrorCode).To(Equal(expectedErr)) + }) }) - Expect(err).To(Succeed()) }) - It("should respond with an error", func() { - Expect(bulkResponse[1].Type).To(Equal(components.V2BulkElementResultType("ERROR"))) - Expect(bulkResponse[1].V2BulkElementResultError.ErrorCode).To(Equal("INSUFFICIENT_FUND")) - }) - }) + } + }) diff --git a/test/e2e/api_transactions_create.go b/test/e2e/api_transactions_create.go index 0da493620..7512dcffb 100644 --- a/test/e2e/api_transactions_create.go +++ b/test/e2e/api_transactions_create.go @@ -3,6 +3,9 @@ package test_suite import ( + "math/big" + "time" + "github.com/formancehq/go-libs/v2/logging" . "github.com/formancehq/go-libs/v2/testing/api" ledger "github.com/formancehq/ledger/internal" @@ -10,8 +13,6 @@ import ( . "github.com/formancehq/ledger/pkg/testserver" "github.com/formancehq/stack/ledger/client/models/components" "github.com/formancehq/stack/ledger/client/models/operations" - "math/big" - "time" "github.com/formancehq/go-libs/v2/metadata" "github.com/formancehq/go-libs/v2/pointer" @@ -22,466 +23,504 @@ import ( ) var _ = Context("Ledger accounts list API tests", func() { - var ( - db = UseTemplatedDatabase() - ctx = logging.TestingContext() - ) - - testServer := NewTestServer(func() Configuration { - return Configuration{ - PostgresConfiguration: db.GetValue().ConnectionOptions(), - Output: GinkgoWriter, - Debug: debug, - NatsURL: natsServer.GetValue().ClientURL(), - } - }) - - BeforeEach(func() { - err := CreateLedger(ctx, testServer.GetValue(), operations.V2CreateLedgerRequest{ - Ledger: "default", - }) - Expect(err).To(BeNil()) - - err = CreateLedger(ctx, testServer.GetValue(), operations.V2CreateLedgerRequest{ - Ledger: "test", - }) - Expect(err).To(BeNil()) - }) + for _, data := range []struct { + description string + numscriptRewrite bool + }{ + {"default", false}, + {"numscript rewrite", true}, + } { + Context(data.description, func() { + var ( + db = UseTemplatedDatabase() + ctx = logging.TestingContext() + ) + testServer := NewTestServer(func() Configuration { + return Configuration{ + PostgresConfiguration: db.GetValue().ConnectionOptions(), + Output: GinkgoWriter, + Debug: debug, + NatsURL: natsServer.GetValue().ClientURL(), + ExperimentalNumscriptRewrite: data.numscriptRewrite, + } + }) - When("creating a transaction", func() { - var ( - events chan *nats.Msg - timestamp = time.Now().Round(time.Second).UTC() - rsp *components.V2Transaction - req operations.V2CreateTransactionRequest - err error - ) - BeforeEach(func() { - events = testServer.GetValue().Subscribe() - req = operations.V2CreateTransactionRequest{ - V2PostTransaction: components.V2PostTransaction{ - Timestamp: ×tamp, - }, - Ledger: "default", - } - }) - JustBeforeEach(func() { - // Create a transaction - rsp, err = CreateTransaction(ctx, testServer.GetValue(), req) - }) - Context("with valid data", func() { BeforeEach(func() { - req = operations.V2CreateTransactionRequest{ - V2PostTransaction: components.V2PostTransaction{ - Metadata: map[string]string{}, - Postings: []components.V2Posting{ - { - Amount: big.NewInt(100), - Asset: "USD", - Source: "world", - Destination: "alice", - }, - }, - Timestamp: ×tamp, - Reference: pointer.For("foo"), - }, + err := CreateLedger(ctx, testServer.GetValue(), operations.V2CreateLedgerRequest{ Ledger: "default", - } + }) + Expect(err).To(BeNil()) + + err = CreateLedger(ctx, testServer.GetValue(), operations.V2CreateLedgerRequest{ + Ledger: "test", + }) + Expect(err).To(BeNil()) }) - It("should be ok", func() { - response, err := GetTransaction( - ctx, - testServer.GetValue(), - operations.V2GetTransactionRequest{ - Ledger: "default", - ID: rsp.ID, - Expand: pointer.For("volumes"), - }, - ) - Expect(err).ToNot(HaveOccurred()) - Expect(*response).To(Equal(components.V2Transaction{ - Timestamp: rsp.Timestamp, - InsertedAt: rsp.InsertedAt, - Postings: rsp.Postings, - Reference: rsp.Reference, - Metadata: rsp.Metadata, - ID: rsp.ID, - PreCommitVolumes: map[string]map[string]components.V2Volume{ - "world": { - "USD": { - Input: big.NewInt(0), - Output: big.NewInt(0), - Balance: big.NewInt(0), - }, + When("creating a transaction", func() { + var ( + events chan *nats.Msg + timestamp = time.Now().Round(time.Second).UTC() + rsp *components.V2Transaction + req operations.V2CreateTransactionRequest + err error + ) + BeforeEach(func() { + events = testServer.GetValue().Subscribe() + req = operations.V2CreateTransactionRequest{ + V2PostTransaction: components.V2PostTransaction{ + Timestamp: ×tamp, }, - "alice": { - "USD": { - Input: big.NewInt(0), - Output: big.NewInt(0), - Balance: big.NewInt(0), + Ledger: "default", + } + }) + JustBeforeEach(func() { + // Create a transaction + rsp, err = CreateTransaction(ctx, testServer.GetValue(), req) + }) + Context("with valid data", func() { + BeforeEach(func() { + req = operations.V2CreateTransactionRequest{ + V2PostTransaction: components.V2PostTransaction{ + Metadata: map[string]string{}, + Postings: []components.V2Posting{ + { + Amount: big.NewInt(100), + Asset: "USD", + Source: "world", + Destination: "alice", + }, + }, + Timestamp: ×tamp, + Reference: pointer.For("foo"), }, - }, - }, - PostCommitVolumes: map[string]map[string]components.V2Volume{ - "world": { - "USD": { - Input: big.NewInt(0), - Output: big.NewInt(100), - Balance: big.NewInt(-100), + Ledger: "default", + } + }) + It("should be ok", func() { + response, err := GetTransaction( + ctx, + testServer.GetValue(), + operations.V2GetTransactionRequest{ + Ledger: "default", + ID: rsp.ID, + Expand: pointer.For("volumes"), }, - }, - "alice": { - "USD": { - Input: big.NewInt(100), - Output: big.NewInt(0), - Balance: big.NewInt(100), + ) + Expect(err).ToNot(HaveOccurred()) + + Expect(*response).To(Equal(components.V2Transaction{ + Timestamp: rsp.Timestamp, + InsertedAt: rsp.InsertedAt, + Postings: rsp.Postings, + Reference: rsp.Reference, + Metadata: rsp.Metadata, + ID: rsp.ID, + PreCommitVolumes: map[string]map[string]components.V2Volume{ + "world": { + "USD": { + Input: big.NewInt(0), + Output: big.NewInt(0), + Balance: big.NewInt(0), + }, + }, + "alice": { + "USD": { + Input: big.NewInt(0), + Output: big.NewInt(0), + Balance: big.NewInt(0), + }, + }, }, - }, - }, - })) + PostCommitVolumes: map[string]map[string]components.V2Volume{ + "world": { + "USD": { + Input: big.NewInt(0), + Output: big.NewInt(100), + Balance: big.NewInt(-100), + }, + }, + "alice": { + "USD": { + Input: big.NewInt(100), + Output: big.NewInt(0), + Balance: big.NewInt(100), + }, + }, + }, + })) - account, err := GetAccount( - ctx, - testServer.GetValue(), - operations.V2GetAccountRequest{ - Address: "alice", - Ledger: "default", - Expand: pointer.For("volumes"), - }, - ) - Expect(err).ToNot(HaveOccurred()) + account, err := GetAccount( + ctx, + testServer.GetValue(), + operations.V2GetAccountRequest{ + Address: "alice", + Ledger: "default", + Expand: pointer.For("volumes"), + }, + ) + Expect(err).ToNot(HaveOccurred()) - Expect(*account).Should(Equal(components.V2Account{ - Address: "alice", - Metadata: metadata.Metadata{}, - Volumes: map[string]components.V2Volume{ - "USD": { - Input: big.NewInt(100), - Output: big.NewInt(0), - Balance: big.NewInt(100), - }, - }, - })) - By("should trigger a new event", func() { - Eventually(events).Should(Receive(Event(ledgerevents.EventTypeCommittedTransactions, WithPayload(bus.CommittedTransactions{ - Ledger: "default", - Transactions: []ledger.Transaction{ConvertSDKTxToCoreTX(rsp)}, - AccountMetadata: ledger.AccountMetadata{}, - })))) + Expect(*account).Should(Equal(components.V2Account{ + Address: "alice", + Metadata: metadata.Metadata{}, + Volumes: map[string]components.V2Volume{ + "USD": { + Input: big.NewInt(100), + Output: big.NewInt(0), + Balance: big.NewInt(100), + }, + }, + })) + By("should trigger a new event", func() { + Eventually(events).Should(Receive(Event(ledgerevents.EventTypeCommittedTransactions, WithPayload(bus.CommittedTransactions{ + Ledger: "default", + Transactions: []ledger.Transaction{ConvertSDKTxToCoreTX(rsp)}, + AccountMetadata: ledger.AccountMetadata{}, + })))) + }) + }) + When("using a reference", func() { + BeforeEach(func() { + req.V2PostTransaction.Reference = pointer.For("foo") + }) + It("should be ok", func() { + Expect(err).To(BeNil()) + }) + When("trying to commit a new transaction with the same reference", func() { + JustBeforeEach(func() { + _, err = CreateTransaction(ctx, testServer.GetValue(), req) + Expect(err).To(HaveOccurred()) + Expect(err).To(HaveErrorCode(string(components.V2ErrorsEnumConflict))) + }) + It("Should fail with "+string(components.V2ErrorsEnumConflict)+" error code", func() {}) + }) + }) }) - }) - When("using a reference", func() { - BeforeEach(func() { - req.V2PostTransaction.Reference = pointer.For("foo") + When("with insufficient funds", func() { + BeforeEach(func() { + req.V2PostTransaction.Postings = []components.V2Posting{{ + Amount: big.NewInt(100), + Asset: "USD", + Source: "bob", + Destination: "alice", + }} + }) + It("should fail", func() { + var expectedErr string + if data.numscriptRewrite { + expectedErr = string(components.V2ErrorsEnumInterpreterRuntime) + } else { + expectedErr = string(components.V2ErrorsEnumInsufficientFund) + } + + Expect(err).To(HaveOccurred()) + Expect(err).To(HaveErrorCode(expectedErr)) + }) }) - It("should be ok", func() { - Expect(err).To(BeNil()) + When("with nil amount", func() { + BeforeEach(func() { + req.V2PostTransaction.Postings = []components.V2Posting{{ + Asset: "USD", + Source: "bob", + Destination: "alice", + }} + }) + It("should fail", func() { + Expect(err).To(HaveOccurred()) + Expect(err).To(HaveErrorCode(string(components.V2ErrorsEnumValidation))) + }) }) - When("trying to commit a new transaction with the same reference", func() { - JustBeforeEach(func() { - _, err = CreateTransaction(ctx, testServer.GetValue(), req) + When("with negative amount", func() { + BeforeEach(func() { + req.V2PostTransaction.Postings = []components.V2Posting{{ + Amount: big.NewInt(-100), + Asset: "USD", + Source: "bob", + Destination: "alice", + }} + }) + It("should fail", func() { Expect(err).To(HaveOccurred()) - Expect(err).To(HaveErrorCode(string(components.V2ErrorsEnumConflict))) + Expect(err).To(HaveErrorCode(string(components.V2ErrorsEnumValidation))) }) - It("Should fail with "+string(components.V2ErrorsEnumConflict)+" error code", func() {}) }) - }) - }) - When("with insufficient funds", func() { - BeforeEach(func() { - req.V2PostTransaction.Postings = []components.V2Posting{{ - Amount: big.NewInt(100), - Asset: "USD", - Source: "bob", - Destination: "alice", - }} - }) - It("should fail", func() { - Expect(err).To(HaveOccurred()) - Expect(err).To(HaveErrorCode(string(components.V2ErrorsEnumInsufficientFund))) - }) - }) - When("with nil amount", func() { - BeforeEach(func() { - req.V2PostTransaction.Postings = []components.V2Posting{{ - Asset: "USD", - Source: "bob", - Destination: "alice", - }} - }) - It("should fail", func() { - Expect(err).To(HaveOccurred()) - Expect(err).To(HaveErrorCode(string(components.V2ErrorsEnumValidation))) - }) - }) - When("with negative amount", func() { - BeforeEach(func() { - req.V2PostTransaction.Postings = []components.V2Posting{{ - Amount: big.NewInt(-100), - Asset: "USD", - Source: "bob", - Destination: "alice", - }} - }) - It("should fail", func() { - Expect(err).To(HaveOccurred()) - Expect(err).To(HaveErrorCode(string(components.V2ErrorsEnumValidation))) - }) - }) - When("with invalid source address", func() { - BeforeEach(func() { - req.V2PostTransaction.Postings = []components.V2Posting{{ - Amount: big.NewInt(-100), - Asset: "USD", - Source: "bob;test", - Destination: "alice", - }} - }) - It("should fail", func() { - Expect(err).To(HaveOccurred()) - Expect(err).To(HaveErrorCode(string(components.V2ErrorsEnumValidation))) - }) - }) - When("with invalid destination address", func() { - BeforeEach(func() { - req.V2PostTransaction.Postings = []components.V2Posting{{ - Amount: big.NewInt(-100), - Asset: "USD", - Source: "bob", - Destination: "alice;test", - }} - }) - It("should fail", func() { - Expect(err).To(HaveOccurred()) - Expect(err).To(HaveErrorCode(string(components.V2ErrorsEnumValidation))) - }) - }) - When("with invalid asset", func() { - BeforeEach(func() { - req.V2PostTransaction.Postings = []components.V2Posting{{ - Amount: big.NewInt(-100), - Asset: "USD//2", - Source: "bob", - Destination: "alice", - }} - }) - It("should fail", func() { - Expect(err).To(HaveOccurred()) - Expect(err).To(HaveErrorCode(string(components.V2ErrorsEnumValidation))) - }) - }) - When("using an idempotency key and a specific ledger", func() { - BeforeEach(func() { - req = operations.V2CreateTransactionRequest{ - IdempotencyKey: pointer.For("foo"), - V2PostTransaction: components.V2PostTransaction{ - Metadata: map[string]string{}, - Postings: []components.V2Posting{ - { - Amount: big.NewInt(100), - Asset: "USD", - Source: "world", - Destination: "alice", - }, - }, - Timestamp: ×tamp, - Reference: pointer.For("foo"), - }, - Ledger: "default", - } - }) - It("should be ok", func() { - Expect(err).To(Succeed()) - Expect(rsp.ID).To(Equal(big.NewInt(1))) - }) - When("creating a ledger transaction with same ik and different ledger", func() { - JustBeforeEach(func() { - rsp, err = CreateTransaction(ctx, testServer.GetValue(), req) + When("with invalid source address", func() { + BeforeEach(func() { + req.V2PostTransaction.Postings = []components.V2Posting{{ + Amount: big.NewInt(-100), + Asset: "USD", + Source: "bob;test", + Destination: "alice", + }} + }) + It("should fail", func() { + Expect(err).To(HaveOccurred()) + Expect(err).To(HaveErrorCode(string(components.V2ErrorsEnumValidation))) + }) }) - It("should not have an error", func() { - Expect(err).To(Succeed()) - Expect(rsp.ID).To(Equal(big.NewInt(1))) + When("with invalid destination address", func() { + BeforeEach(func() { + req.V2PostTransaction.Postings = []components.V2Posting{{ + Amount: big.NewInt(-100), + Asset: "USD", + Source: "bob", + Destination: "alice;test", + }} + }) + It("should fail", func() { + Expect(err).To(HaveOccurred()) + Expect(err).To(HaveErrorCode(string(components.V2ErrorsEnumValidation))) + }) }) - }) - }) - When("using a negative amount in a script", func() { - BeforeEach(func() { - req = operations.V2CreateTransactionRequest{ - IdempotencyKey: pointer.For("testing"), - V2PostTransaction: components.V2PostTransaction{ - Metadata: map[string]string{}, - Script: &components.V2PostTransactionScript{ - Plain: `send [COIN -100] ( + When("with invalid asset", func() { + BeforeEach(func() { + req.V2PostTransaction.Postings = []components.V2Posting{{ + Amount: big.NewInt(-100), + Asset: "USD//2", + Source: "bob", + Destination: "alice", + }} + }) + It("should fail", func() { + Expect(err).To(HaveOccurred()) + Expect(err).To(HaveErrorCode(string(components.V2ErrorsEnumValidation))) + }) + }) + When("using an idempotency key and a specific ledger", func() { + BeforeEach(func() { + req = operations.V2CreateTransactionRequest{ + IdempotencyKey: pointer.For("foo"), + V2PostTransaction: components.V2PostTransaction{ + Metadata: map[string]string{}, + Postings: []components.V2Posting{ + { + Amount: big.NewInt(100), + Asset: "USD", + Source: "world", + Destination: "alice", + }, + }, + Timestamp: ×tamp, + Reference: pointer.For("foo"), + }, + Ledger: "default", + } + }) + It("should be ok", func() { + Expect(err).To(Succeed()) + Expect(rsp.ID).To(Equal(big.NewInt(1))) + }) + When("creating a ledger transaction with same ik and different ledger", func() { + JustBeforeEach(func() { + rsp, err = CreateTransaction(ctx, testServer.GetValue(), req) + }) + It("should not have an error", func() { + Expect(err).To(Succeed()) + Expect(rsp.ID).To(Equal(big.NewInt(1))) + }) + }) + }) + When("using a negative amount in a script", func() { + BeforeEach(func() { + req = operations.V2CreateTransactionRequest{ + IdempotencyKey: pointer.For("testing"), + V2PostTransaction: components.V2PostTransaction{ + Metadata: map[string]string{}, + Script: &components.V2PostTransactionScript{ + Plain: `send [COIN -100] ( source = @world destination = @bob )`, - Vars: map[string]interface{}{}, - }, - }, - Ledger: "default", - } - }) - It("should fail with "+string(components.V2ErrorsEnumCompilationFailed)+" code", func() { - Expect(err).NotTo(Succeed()) - Expect(err).To(HaveErrorCode(string(components.V2ErrorsEnumCompilationFailed))) - }) - }) - When("using a negative amount in the script with a variable", func() { - BeforeEach(func() { - req = operations.V2CreateTransactionRequest{ - IdempotencyKey: pointer.For("testing"), - V2PostTransaction: components.V2PostTransaction{ - Metadata: map[string]string{}, - Script: &components.V2PostTransactionScript{ - Plain: `vars { + Vars: map[string]interface{}{}, + }, + }, + Ledger: "default", + } + }) + + var expectedErr string + if data.numscriptRewrite { + expectedErr = string(components.V2ErrorsEnumInterpreterRuntime) + } else { + expectedErr = string(components.V2ErrorsEnumCompilationFailed) + } + + It("should fail with "+expectedErr+" code", func() { + Expect(err).NotTo(Succeed()) + Expect(err).To(HaveErrorCode(expectedErr)) + }) + }) + When("using a negative amount in the script with a variable", func() { + BeforeEach(func() { + req = operations.V2CreateTransactionRequest{ + IdempotencyKey: pointer.For("testing"), + V2PostTransaction: components.V2PostTransaction{ + Metadata: map[string]string{}, + Script: &components.V2PostTransactionScript{ + Plain: `vars { monetary $amount } send $amount ( source = @world destination = @bob )`, - Vars: map[string]interface{}{ - "amount": "USD -100", + Vars: map[string]interface{}{ + "amount": "USD -100", + }, + }, }, - }, - }, - Ledger: "default", - } - }) - It("should fail with "+string(components.V2ErrorsEnumCompilationFailed)+" code", func() { - Expect(err).NotTo(Succeed()) - Expect(err).To(HaveErrorCode(string(components.V2ErrorsEnumCompilationFailed))) - }) - }) - Context("with error on script", func() { - BeforeEach(func() { - req = operations.V2CreateTransactionRequest{ - IdempotencyKey: pointer.For("testing"), - V2PostTransaction: components.V2PostTransaction{ - Metadata: map[string]string{}, - Script: &components.V2PostTransactionScript{ - Plain: `XXX`, - Vars: map[string]interface{}{}, - }, - }, - Ledger: "default", - } - }) - It("should fail with "+string(components.V2ErrorsEnumCompilationFailed)+" code", func() { - Expect(err).NotTo(Succeed()) - Expect(err).To(HaveErrorCode(string(components.V2ErrorsEnumCompilationFailed))) - }) - }) - Context("with no postings", func() { - BeforeEach(func() { - req = operations.V2CreateTransactionRequest{ - IdempotencyKey: pointer.For("testing"), - V2PostTransaction: components.V2PostTransaction{ - Metadata: map[string]string{}, - Script: &components.V2PostTransactionScript{ - Plain: `vars { + Ledger: "default", + } + }) + + var expectedErr string + if data.numscriptRewrite { + expectedErr = string(components.V2ErrorsEnumInterpreterRuntime) + } else { + expectedErr = string(components.V2ErrorsEnumCompilationFailed) + } + It("should fail with "+expectedErr+" code", func() { + Expect(err).NotTo(Succeed()) + Expect(err).To(HaveErrorCode(expectedErr)) + }) + }) + Context("with error on script", func() { + BeforeEach(func() { + req = operations.V2CreateTransactionRequest{ + IdempotencyKey: pointer.For("testing"), + V2PostTransaction: components.V2PostTransaction{ + Metadata: map[string]string{}, + Script: &components.V2PostTransactionScript{ + Plain: `XXX`, + Vars: map[string]interface{}{}, + }, + }, + Ledger: "default", + } + }) + var expectedErr string + if data.numscriptRewrite { + expectedErr = string(components.V2ErrorsEnumInterpreterParse) + } else { + expectedErr = string(components.V2ErrorsEnumCompilationFailed) + } + It("should fail with "+expectedErr+" code", func() { + Expect(err).NotTo(Succeed()) + Expect(err).To(HaveErrorCode(expectedErr)) + }) + }) + Context("with no postings", func() { + BeforeEach(func() { + req = operations.V2CreateTransactionRequest{ + IdempotencyKey: pointer.For("testing"), + V2PostTransaction: components.V2PostTransaction{ + Metadata: map[string]string{}, + Script: &components.V2PostTransactionScript{ + Plain: `vars { monetary $amount } set_tx_meta("foo", "bar") `, - Vars: map[string]interface{}{ - "amount": "USD 100", + Vars: map[string]interface{}{ + "amount": "USD 100", + }, + }, }, - }, - }, - Ledger: "default", - } - }) - It("should fail with "+string(components.V2ErrorsEnumNoPostings)+" code", func() { - Expect(err).NotTo(Succeed()) - Expect(err).To(HaveErrorCode(string(components.V2ErrorsEnumNoPostings))) - }) - }) - When("with metadata override", func() { - BeforeEach(func() { - req = operations.V2CreateTransactionRequest{ - IdempotencyKey: pointer.For("testing"), - V2PostTransaction: components.V2PostTransaction{ - Metadata: map[string]string{ - "foo": "baz", - }, - Script: &components.V2PostTransactionScript{ - Plain: `send [COIN 100] ( + Ledger: "default", + } + }) + It("should fail with "+string(components.V2ErrorsEnumNoPostings)+" code", func() { + Expect(err).NotTo(Succeed()) + Expect(err).To(HaveErrorCode(string(components.V2ErrorsEnumNoPostings))) + }) + }) + When("with metadata override", func() { + BeforeEach(func() { + req = operations.V2CreateTransactionRequest{ + IdempotencyKey: pointer.For("testing"), + V2PostTransaction: components.V2PostTransaction{ + Metadata: map[string]string{ + "foo": "baz", + }, + Script: &components.V2PostTransactionScript{ + Plain: `send [COIN 100] ( source = @world destination = @bob ) set_tx_meta("foo", "bar")`, - Vars: map[string]interface{}{}, - }, - }, - Ledger: "default", - } - }) - It("should fail with "+string(components.V2ErrorsEnumMetadataOverride)+" code", func() { - Expect(err).NotTo(Succeed()) - Expect(err).To(HaveErrorCode(string(components.V2ErrorsEnumMetadataOverride))) - }) - }) - When("with dry run mode", func() { - BeforeEach(func() { - req = operations.V2CreateTransactionRequest{ - IdempotencyKey: pointer.For("testing"), - V2PostTransaction: components.V2PostTransaction{ - Metadata: map[string]string{}, - Script: &components.V2PostTransactionScript{ - Plain: `send [COIN 100] ( + Vars: map[string]interface{}{}, + }, + }, + Ledger: "default", + } + }) + It("should fail with "+string(components.V2ErrorsEnumMetadataOverride)+" code", func() { + Expect(err).NotTo(Succeed()) + Expect(err).To(HaveErrorCode(string(components.V2ErrorsEnumMetadataOverride))) + }) + }) + When("with dry run mode", func() { + BeforeEach(func() { + req = operations.V2CreateTransactionRequest{ + IdempotencyKey: pointer.For("testing"), + V2PostTransaction: components.V2PostTransaction{ + Metadata: map[string]string{}, + Script: &components.V2PostTransactionScript{ + Plain: `send [COIN 100] ( source = @world destination = @bob )`, - Vars: map[string]interface{}{}, - }, - }, - DryRun: pointer.For(true), - Ledger: "default", - } - }) - It("should be ok", func() { - Expect(err).To(BeNil()) + Vars: map[string]interface{}{}, + }, + }, + DryRun: pointer.For(true), + Ledger: "default", + } + }) + It("should be ok", func() { + Expect(err).To(BeNil()) + }) + }) }) - }) - }) - When("creating a transaction on the ledger v1 with old variable format", func() { - var ( - err error - response *operations.CreateTransactionResponse - ) - BeforeEach(func() { - v, _ := big.NewInt(0).SetString("1320000000000000000000000000000000000000000000000001", 10) - response, err = testServer.GetValue().Client().Ledger.V1.CreateTransaction( - ctx, - operations.CreateTransactionRequest{ - PostTransaction: components.PostTransaction{ - Metadata: map[string]any{}, - Script: &components.PostTransactionScript{ - Plain: `vars { + When("creating a transaction on the ledger v1 with old variable format", func() { + var ( + err error + response *operations.CreateTransactionResponse + ) + BeforeEach(func() { + v, _ := big.NewInt(0).SetString("1320000000000000000000000000000000000000000000000001", 10) + response, err = testServer.GetValue().Client().Ledger.V1.CreateTransaction( + ctx, + operations.CreateTransactionRequest{ + PostTransaction: components.PostTransaction{ + Metadata: map[string]any{}, + Script: &components.PostTransactionScript{ + Plain: `vars { monetary $amount } send $amount ( source = @world destination = @bob )`, - Vars: map[string]interface{}{ - "amount": map[string]any{ - "asset": "EUR/12", - "amount": v, + Vars: map[string]interface{}{ + "amount": map[string]any{ + "asset": "EUR/12", + "amount": v, + }, + }, }, }, + Ledger: "default", }, - }, - Ledger: "default", - }, - ) - }) - It("should be ok", func() { - Expect(err).To(Succeed()) - Expect(response.TransactionsResponse.Data[0].Txid).To(Equal(big.NewInt(1))) + ) + }) + It("should be ok", func() { + Expect(err).To(Succeed()) + Expect(response.TransactionsResponse.Data[0].Txid).To(Equal(big.NewInt(1))) + }) + }) }) - }) + } })